/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2016年11月17日
 * V4.0
 */
package com.jphenix.driver.cluster;

import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletResponse;

import com.jphenix.driver.json.Json;
import com.jphenix.driver.log.xlogc.XLogFilter;
import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.driver.regc.RegistCenterFilter;
import com.jphenix.driver.serialize.XmlSerializer;
import com.jphenix.driver.serialno.FSN;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanFactoryManager;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.kernel.script.ScriptLoader;
import com.jphenix.servlet.filter.FilterExplorer;
import com.jphenix.share.lang.RollIterator;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.lang.SListMap;
import com.jphenix.share.lang.SString;
import com.jphenix.share.tools.Base64;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.tools.HttpCall;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.ServiceInfo;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.beans.ITriggerInit;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Register;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.exceptions.HttpException;
import com.jphenix.standard.exceptions.TimeOutException;
import com.jphenix.standard.servlet.IActionContext;
import com.jphenix.standard.servlet.ICluster;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;
import com.jphenix.standard.servlet.IServletConst;
import com.jphenix.standard.viewhandler.INodeHandler;
import com.jphenix.standard.viewhandler.IViewHandler;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

/**
 * 集群服务过滤器
 * 
 * 注意：如果在配置文件中启用了集群，但是集群并没有工作，可能是本机IP没有配置到集群信息中
 * 
 * 路由风暴（致命故障）：ABC三台服务器，A发现本地无法调用该脚本，路由到B，B发现无法调用该脚本，路由到C，C无法调用该脚本，路由到A
 * 
 * 
 * 集群服务器提供3个功能：
 * 
 * 1. 集群服务：标记为集群服务的程序，在程序中有一个变量，为true时是主服务器，
 *    用这个变量判断是否为主服务器，确保集群中只有一台服务器在执行程序
 *    
 * 2. 广播服务：通常由动作类被设定为广播(也可以是服务)，调用这个动作时，会自动广播给所
 *    有有效集群服务器这个同名动作，广播动作没有返回值（有啥返回值，返哪台服务器的值？）
 *    
 * 3. 集群动作：标记为集群动作的程序，可以由内部集群中任意一台服务器调用这个动作，则只有
 *    主服务器才响应这个动作，并且获取返回值。
 * 
 * 附属功能：
 * 
 * 1.提供统一的集群服务器时间
 * 2.判断是否为集群内部服务器调用（判断服务器地址是否在集群服务器配置信息中）
 * 3.是否为主服务器
 * 
 * _cluster_mode_ 集群处理模式：
 * 
 * 1 检查当前服务器运行状态
 * 返回：{name:服务器名,master:是否为主服务器,time:毫秒值}
 * 
 * 2 集群内调用
 * 
 * 3 反向触发调用服务返回值处理
 * 
 * X 4 返回当前服务器是否可以联通目标服务器（已废弃）
 *   废弃原因：同群组服务器的权限都是相同的，同群组中别的服务器能调用到，当前服务器也就能调用到。不允许同群组服务器中，其中一台
 *            与其它服务器不同。如果存在该情况，需要将该服务器独立作为一个新的群组。
 * 
 * X 5 同集群服务器中，委托调用目标服务器 （已废弃）
 *   废弃原因：因为通常这种情况发生于外网服务器通过Nginx调用内部负载均衡的多台服务器。
 *            对于外网服务器来说，内网做负载均衡的群组多台服务器，是一台服务器（由Nginx动态指向其中一台服务器）
 *            在这种情况时，外网服务器发送心跳包，在5分钟之内，群组内部服务器都能收到外网服务器的心跳包，不需要做委托调用，
 *            增加调用环节
 *            
 * 6.复合串行集群中，查找其它可以访问目标服务器动作
 * 
 * 7.执行代理中转调用
 * 
 * 8.内部触发调用。通过报文头中的 cluster_trigger_key 判断触发容器中哪个触发实例
 * 
 * <cluster disabled="0" disabledInvokeLoadBalancing="" time_out="10000" fixed_master="0" reverse_trigger_time_out=""  reverse_trigger_interval_time=""  addition_ip="">
 *  <server name="E1" group="group1" ip="127.0.0.1" dis_send="" url="http://localhost:8080" context="/contextpath" disabled="0" allow="127.*.*.*,192.168.*.*" sessionKeyName=""/>
 * </cluster>
 * 
 * disabled 是否禁用集群功能  0启用  1禁用
 * 
 * 集群远程调用负载均衡算法：平均分摊方法
 * disabledInvokeLoadBalancing  是否禁用集群远程调用负载均衡 0（默认）允许 1禁止
 * 
 * time_out 集群间通信超时时间
 * fixed_master 是否固定集群主服务器（默认配置信息中的第一台为主服务器）  
 *              0不固定，如果第一台信息无响应，第二台会成为主服务器，直到第一台有响应为止
 *              1固定，无论第一台是否有响应，都是主服务器
 * reverse_trigger_time_out 反向触发调用服务等待返回值超时时间，单位：毫秒。默认5分钟
 * reverse_trigger_interval_time 反向触发调用服务检查返回值间隔时间，单位：毫秒。默认100毫秒
 * addition_ip 用逗号分割的ip地址。除了集群服务器的ip地址，加上这些ip地址，控制只有这些ip
 *                  地址才能访问设置了受限制访问的脚本动作类（仅允许集群服务器访问的脚本动作类） 
 *                  支持通配符 * 。比如 10.*.*.*       10.40.*.* 等等
 *                  如果遇到通配符*，那么后面的ip地址就不做匹配了，算作匹配。
 *                  
 * 集群服务器信息元素：
 * name              服务器名，可为空，默认为s[信息序号，从1开始] 比如 主服务器为 s1 其余服务器为 s2 s3....
 * ip                服务器ip地址，用来闲置集群服务器间通信的范围
 * url               集群服务器通信url，为网站根url，不包含虚拟路径
 * context           虚拟路径 （其它代理服务需要单独用到虚拟路径，用来判断本地跟目标虚拟路径是否相同，有必要）如果没有虚拟路径，为空（不为/）
 * disabled          是否禁用当前集群服务器元素
 * group             分组。如果当前服务器的分组名为 group1，那么所有分组名为group1的服务为一组集群，集群作用仅针对当前分组
 * allow             允许外部服务器访问的IP地址规则
 * dis_send          禁止发送的服务器名，多个用逗号分割，该属性用于本地测试复杂网络中，多台集群应用相互调用功能
 *                   模拟无法接收方法：在发送服务器端设置禁止发送到该服务器即可
 * sessionKeyName    该系统会话主键名（默认为：JSESSIONID）
 * 
 * agent_balance (该参数已废弃，没有用到)服务器的外部是否存在类似Nginx等负载均衡，如果存在，外部服务器通过Nginx调用内部集群时，是随机访问到其中一台服务器
 * 
 * 
 * 请求报文头中的值：
 * 
 * _cluster_call_  发起请求的集群服务器名
 * 
 * _cluster_upload_script_ 调用目标集群时，该请求是否为上传模式（上传文件模式），该值为由目标脚本处理上传请求
 * 
 * 
 * ******************************************************************************************************************************
 * 
 * 注意：常驻内存优先顺序为99，也就是过滤器管理类初始化时，首先初始化它，因为其它类会引用该类
 *       比如动作管理类过滤器
 *       
 *       集群中的服务器名是唯一的，无论是否在不同的群组（这是可以做到的，没必要故意起同样名字，也很容易混淆看错，程序处理起来也复杂）
 *       
 *       脚本内部调用其他服务器脚本（程序路由，分布式运行）功能，只能调用目标服务器中的非动作脚本
 *       
 *       通常并行集群用在后台时，调用这个后台都是通过Nginx实现的，由Nginx做负载均衡。通过程序实现负载均衡后，不使用Nginx是不现实的，因为负载均衡这方面
 *       是Nginx或者Apache，或者其它反向代理工具的强项，开发个超过专用工具的功能没有意义，也不现实。所以在设计时，需要认可使用反向代理工具这种情况。
 *       如果使用了外部反向代理工具，就意味着调用方无法调用到目标群组中每个服务器（由负载均衡随机指派群组中其中一台服务器）
 *       
 *       解决方法：在这种环境（外网服务器通过Nginx访问内网服务器集群，内网服务器无法直接访问外网服务器）中，内网其中一台服务器通过反向触发调用外部服务器，
 *       发现无法接收到外网服务器的心跳包，则轮询同分组（Nginx内网服务器集群）中所有服务器，找到可以接收到外网服务器心跳包的目标服务器（mode=4），然后
 *       委托该服务器将数据以反向触发方式传递给外网服务器
 *       
 *       修正解决方法190325：上面这种情况，外部一台服务器，通过Nginx访问内部并行集群中的多台服务器。做反向触发方式调用时，外网接受反向触发请求并处理完毕后，将返回信息返回给那台服务器，就遇到了问题，返回数据采用主动调用方式（发心跳包时顺带返回信息）
 *       主动调用到的内部并行集群中的服务器，并不一定是发起的那台服务器。
 *       
 *       ***放弃（弱化，不在这方面下功夫）软件实现负载均衡，认可使用外部反向代理程序实现负载均衡***
 *       
 *       有时候会出现其中一台服务器无法发心跳包到另外一台服务器，但网络是通的。就需要手动执行检测
 *       
 *       比如： http://127.0.0.1/虚拟路径/_cluster_.ha?_cluster_key=当前服务器名
 *       
 *       目标脚本路由，在并行集群中（负载均衡），其中一台服务器发现不能直接调用目标脚本，需要周边能访问的服务器路由，排除请求同群组服务器，也就是说，并行集群（负债均衡）中的服务器网络访问权限都是相同的。比如并行集群A,B,C服务器，A服务器无法直接访问
 *       A会请求可以访问的其他服务器，但不包含B，C服务器
 *       
 * ******************************************************************************************************************************
 * 
 * 
 * 2018-05-21 由于配置失误，集群信息中，有的服务器并不支持集群，导致集群内其它服务器频繁调用集群指令检查其服务状态，在这台不支持的服务器上频繁显示404错误。
 *            解决方法：在刷新方法中，发现目标返回404错误，则输入错误日志后，不再刷新该服务器信息
 * 
 * 2018-05-24 遇到这种情况， 前台服务器通过后台Nginx调用被Nginx分发的多台后台服务器。即时后台设置了集群，前台也只能指向后台的Nginx服务器，并不是实际的其中一台后台服务器。
 *            目前脚本中使用 <%@center(XXXXXX)%> 方式调用后台，会直接调用后台的主服务器。但正好Nginx分发到了另外的非主服务器，程序就会报错，说找不到目标主服务器。
 *            
 *            所以修改了程序，将 @center 这个center，可以为指定的一台服务器的名字。优先匹配分组名，如果都匹配不上，就认定为服务器名。
 *            
 *            另外，如果找不到目标分组中的主服务器，就用目标分组列表中的第一台服务器
 *            
 *            返回的本机集群信息，包含了当前本机的启动时间和运行时长
 *            
 *            目前还不支持负载均衡，调用负载均衡方法时，默认返回该分组中第一台服务器
 *            
 * 2018-05-25 服务器A调用远程服务器B时，返回的XML信息中，root节点包含服务器的分组名属性group，和服务器名name属性。
 *            因为服务器A调用的可能是Nginx，由Nginx分发多个服务器中。所以从日志中不确定到底调用的哪个后台服务器，查日志很麻烦
 *            
 * 2018-06-11 增加了目标服务器错误信息，用于配置排错
 * 
 * 2018-06-14 远程调用，目标返回值为null（即NullVO），结果将这个VO返回到调用的程序中，导致程序报错。
 * 2018-06-28 增加了判断本次请求是否由集群内部服务器发起的方法
 *            允许将指定服务器配置成，支持未登记（未在配置文件中）服务器作为本机分组集群中的一员。如果是未登记服务访问，仅支持访问，不支持反向访问（调用未登记的服务器）
 * 2018-07-02 为服务器配置信息增加了自定义参数设置和获取方法
 * 2018-07-09 如果在配置文件中，如果禁用了集群配置信息，避免了集群中其他服务器调用集群action到本机时，本机产生报错信息（不认集群动作，提示找不到该动作）
 * 2018-07-18 修改了日志输出方法
 * 2018-07-23 增加了检测接收消息超时功能
 * 2018-07-24 实现了简单的负载均衡（通过目标被调用次数，返回最小值的服务器信息）
 * 2018-07-25 完成了前置反向代理负载均衡时，反向触发调用的问题
 *            在检查刷新其他服务器状态时，如果对方无反馈，会被自动disabled，但是不能这样，否则那台服务器恢复以后，就再也检查不到了
 *            检查接收状态线程中，去掉检查本地服务器信息
 * 2018-07-26 为本机服务器信息VO中增加了服务启动时间
 *            增加了返回当前服务器时间校准差值
 * 2018-09-13 简化了整个架构中日志初始化步骤
 * 2018-10-18 通过服务器主键获取服务器信息的方法中，如果没获取到服务器信息，则将传入值当作分组名，获取集群分组中指定的服务器信息
 *            修改了判断是否由集群调用的方法错误
 * 2018-10-19 修改了是否返回主服务器还是负债均衡返回任意服务器判断错误
 * 2018-12-12 增加了传入文件对象参数。增加了调用远程动作脚本，并返回输出的结果
 * 2018-12-13 完善了注释
 * 2019-01-08 增加了请求模式0，同1，返回当前服务器信息（这样便于在浏览器中请求，查询问题节点）
 *            弃用了invalidServer，请看变量ServerInfoVO.invalidServer声明处的注释
 * 2019-01-31 在调用集群方法时，把调用者类信息传入，方便在日志中查看调用者
 * 2019-02-28 修改了远程方法调用时报错
 * 2019-03-25 反向触发调用中，外部服务器响应后，将信息通过Nginx负载均衡发回给内部服务器，接收服务器需要判断是否为发起服务器，如果
 *            不是，需要将信息转发给内部同分组的发起服务器
 * 2019-03-25 完善了反向触发调用的日志信息
 * 2019-03-25 完善了日志信息
 * 2019-04-01 完善了日志信息
 * 2019-05-11 增加了心跳包防止阻塞机制
 * 2019-06-13 获取当前群组中，其它服务器信息序列（除去本机）
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 *            去掉了检测本机当前项目的心跳线程
 *            
 * 2019-06-26 去掉了返回序列号头值方法，改为返回服务器索引值
 *            修改了防呆检测线程的启动位置
 *            
 * 2019-06-29 增加了线程启动日志，用于调试跟踪集群信息
 * 2019-07-01 关于防呆线程，如果目标服务器下线，但因为操作系统原因，目标服务器的监听端口并没有释放，会导致本机向目标服务器发送心跳请求时阻塞。
 *            防呆线程发现心跳线程长时间没有刷新状态，在执行终止该心跳线程（stop方法）后，重新启动线程（start），实际上并没有启动。
 *            针对这种情况，在终止心跳线程后，释放掉该线程的类实例，重新构造心跳线程并启动
 *            
 * 2019-07-09 简化了集群中反向触发调用逻辑，去掉了模式 4，5。具体原因已经写在注释中。增加了反向触发调用时废弃信息回收机制。
 * 2019-07-16 简化了调用目标服务器URL
 * 2019-08-03 修改了callSelf方法
 * 2019-08-05 修改了远程方法调用错误
 * 2019-09-11 增加了调用集群中指定服务器的相关常用方法
 * 2019-09-12 增加了调用集群中常用的方法
 * 2019-09-18 按照实际情况修改了远程调用方法
 * 2019-09-19 修改了获取提交参数，仅从url中获取提交参数，避免从报文体中获取完提交参数后，又要获取提交数据流而报错
 * 2019-09-20 修改了调用集群中指定服务器（代理方式）方法的错误
 * 2019-09-27 增加了代理中转模式，测试并解决了发现的问题
 * 2019-10-22 新增用于在本地调试模拟复杂网络环境中集群服务器相互调用场景。在配置信息server节点中增加了dis_send，禁止发送心跳包到目标服务器的名字（多个用逗号分割）
 * 2019-10-23 修改了测试时发现的问题
 * 2019-12-10 增加了集群内部上传文件方法
 * 2019-12-19 修改了拼装参数解析参数方法，增加了调制解调数组参数
 * 2019-12-23 修改了集群间数据传送调制解调方法，采用独立的序列化功能执行（注意：新版本与老版本集群数据格式不同）
 * 2020-01-20 修改了调用参数序列化与反序列化时发现的错误（无法处理动作类返回信息）
 * 2020-03-15 去掉了心跳包防止阻塞线程，实际证明没什么用处
 * 2020-03-19 解决了没初始化完时，响应其它服务器请求时报空指针的错误
 * 2020-03-30 解决了路由风暴问题。取消了同群组内路由（没有实际用处）
 * 2020-04-15 在调用报文和返回报文中增加了序列号，因为日志中记录是多线程的，调用报文后面跟着的返回报文并不一定是本次调用返回的，可能是另外一个线程调用后返回的。
 * 2020-06-18 在配置文件中增加了指定服务器会话主键名信息
 * 2020-07-11 下载方法中，设置了本机名到头信息中
 * 2020-07-11 去掉了远程调用时，返回 EmptyValue类，用来替代null，没这个必要
 * 2020-07-14 增加了获取按照分组分类的服务器信息容器方法
 * 2020-08-26 调用本机脚本拦截异常从Exception改为Throwable
 * 2021-03-17 除去废弃引用代码，适配注册中心代码
 * 
 * 
 * 目前遇到的问题：
 * 
 * 反向触发调用时，服务器A委托服务器B调用服务器O，目前的设计是，服务器B收到服务器O返回的消息后，将消息返回给服务器A。
 * 
 * 但实际情况是这样的：服务器A委托服务器B调用服务器O，但是服务器O通过Nginx负载均衡，将返回信息给了服务器C，那服务器C就莫名其妙了，啥玩意啊，废弃。
 * 结果服务器B，服务器A都在等待中超时。
 * 
 * 需要改成这样的：
 * 
 * 服务器A（暂时没有被服务器O发心跳包调用到）委托服务器B调用服务器O。服务器发起请求后，等待接收返回信息。服务器B替服务器A发送完消息后结束任务。
 * 服务器O收到请求并处理后，将返回信息（包含服务器A源头信息）通过Nginx发送给服务器C（随机的），服务器C解析报文中发现发起源头是服务器A
 * ，将消息转给服务器A，服务器A开开心心收到信息。
 * 
 * com.jphenix.driver.cluster.ClusterFilter
 * @author MBG
 * 2016年11月17日
 */
@ClassInfo({"2021-03-17 21:02","集群服务过滤器"})
@BeanInfo({"clusterfilter"})
@Register({"filtervector"})
@Running({"99","","","destroy"})
public class ClusterFilter extends ABase implements IFilter,ICluster,IBeanRegister {

	private final int    MSG_SIZE                      = 10;              //消息队列深度（不用设置太多，没必要
	private       int    REVERSE_TRIGGER_TIME_OUT      = 0;               //反向触发调用服务等待返回值超时时间（毫秒）
	private       int    REVERSE_TRIGGER_INTERVAL_TIME = 0;               //反向触发调用服务获取返回值间隔时间（毫秒）
	private final String CLUSTER_ACTION                = "/_cluster_.ha"; //调用其它集群过滤器的动作路径
	
	//服务器信息序列容器 key:分组主键 value:服务器信息容器
	private Map<String,SListMap<ServerInfoVO>> groupServerMap = new HashMap<>();
	
	//服务器容器 key:服务器主键 value:服务器信息容器
	private SListMap<ServerInfoVO> serverMap = new SListMap<>();
	
	//服务器消息容器（用于反向触发调用服务）  Object[2]  0脚本名  1传入参数
	private Map<String,RollIterator<Object[]>> msgMap = new HashMap<>();
	
	private SListMap<Long> tokenMap          = new SListMap<>(); //token容器
	
	private TokenTimeOutThread ttot          = null; //检测token超时线程
	
	private ScriptLoader   sl                = null; //脚本类加载器  
	
	//集群服务器中的ip地址序列（对内）
	private List<String> ipList              = new ArrayList<>();
	//需要获取服务器名的程序序列
	private List<ITriggerInit> initBeanList  = new ArrayList<>();
	
	//需要做触发的类序列
	private SListMap<IClusterTrigger> triggerBeans = new SListMap<>();
	
	private String localServerTempName   = null;       //本地服务器名（只是在启动时识别当前服务器时使用）
	private ServerInfoVO localServerVO   = null; //本地服务器信息

	private String  masterServerName     = "";    //当前分组主服务器名（当前变量只在内部用，不对外）
	
	private boolean fixedMaster          = false; //是否固定主服务器（始终为第一台服务器） 
	private boolean disabled             = false; //是否禁用集群功能
	private boolean isTempServerName     = true;  //是否为临时服务器名
	private boolean invokeLoadBalancing  = true;  //是否启用远程调用负载均衡（默认为允许）
	private boolean findMeNameOK         = false; //是否在集群配置信息中找到本机服务器的配置信息
	private int     timeOut              = 3000;  //调用目标服务器超时时间
	private int     clusterMode          = 0;     //未初始化完毕  1集群模式  2非集群模式
	private long    amendTime            = -1;    //参考主服务器校准后的时间   校准时间并不耗费太多时间，可以忽略不计，可以重复校准
	private List<HeartbeatThread> hbList = null;  //心跳线程序列
	
	//反向触发调用服务返回消息容器 key:token value:返回值
	private Map<String,Object> reMsgMap  = new HashMap<>();
	private int tokenIndex               = 0;  //token索引
	private int invokeCount              = 0;  //被调用次数
	private int invokeClearFlag          = 0;  //调用次数清零标识，每次清零时，该值会发生变化
	private ReceiveTimeOutThread rtoThread          = null; //检测接收信息超时线程
	private XmlSerializer        xmlSerializer      = null; //实例序列化处理类
	private long                 snIndex            = 0;    //序列号
	
	
	
	
	//心跳包防止阻塞线程
    //有时候因为操作系统原因，远程服务器重置或者无相应，导致心跳线程中执行refreshCluster卡死阻塞
	//实践证明：没有起到实际作用
	//private CheckStiffThread csThread = null;
	
	//判断是否允许访问的IP信息序列（对外）
	/*
	 * ip地址访问过滤
	 * 对内： ip地址完全匹配才能允许集群访问
	 * 
	 * 对外：有一些动作类虽然不是用的集群本身功能调用，比如外部系统，比如文件上传等复杂数据动作
	 *           这些动作也需要仅允许集群内部服务器才能访问
	 *           
	 *           如果有集群外的服务器也需要调用，就需要在集群配置文件的属性addition_ip中增加这个请求的ip地址
	 */
	private List<String[]> allowIpList = new ArrayList<String[]>();
	
	private RegistCenterFilter rcf = null;  //注册中心过滤器
	
	
	/*
	 * 实践证明：没有起到实际作用
	 **
	 * 防止心跳包阻塞线程
	 * @author MBG
	 * 2019年5月11日
	 *
	private class CheckStiffThread extends Thread {
		
		**
		 * 构造函数
		 * @author MBG
		 *
		public CheckStiffThread() {
			super("ClusterFilter-CheckStiffThread");
		}
		
		
		**
		 * 覆盖方法
		 *
		@Override
        @SuppressWarnings("deprecation")
		public void run() {
			while(true) {
				try {
					Thread.sleep(6000);
				}catch(Exception e) {
					e.printStackTrace();
				}
				long now = System.currentTimeMillis(); //当前时间
				HeartbeatThread ele;                   //轮训线程元素
				HeartbeatThread newThread;             //新线程
				for(int i=0;i<hbList.size();i++) {
					ele = hbList.get(i);
					if((now-ele.getStartTime())>60000) {
						//超过1分钟都没获取到心跳
						warning("ClusterFilter The HeartbeatThread: Server:["+ele.getVO().name+"] Group:["+ele.getVO().group+"] Has Choke Begin Restart");
						ele.getVO().validSend = false; //标记发送失败
						
						try {
							*
							 * 注意：如果是因为操作系统原因，比如远程服务器已经下线，java端口已经释放（但实际上系统仍然监听该端口，所有者自动变为system）
							 *       当前服务器发送心跳包请求到那个已经下线的服务器时，线程会阻塞住。这个时候执行心跳线程终止后，无法再次start启动该线程。
							 *       所以不能在这里先执行stop方法，然后执行start方法
							 *
							ele.stop();
						}catch(Exception e) {}
						
						//在这里新起一个线程
						newThread = new HeartbeatThread(ele.siVO,ele.index);
						hbList.set(i,newThread);
						ele = null;
						try {
							newThread.start();
						}catch(Exception e) {}
					}
				}
			}
		}
	}
	*/
	
	/**
	 * 执行初始化线程
	 * @author MBG
	 * 2017年4月21日
	 */
	private class InitThread extends Thread {
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public InitThread() {
			super("ClusterFilter-InitThread");
		}
		
		/**
		 * 覆盖方法
		 */
		@Override
        public void run() {
			
			//放入调用目标服务器超时时间
			ThreadSession.put("_httpcall_connect_time_out",String.valueOf(timeOut));
			ThreadSession.put("_httpcall_read_time_out",String.valueOf(timeOut));
			
			//先验证本机服务器名
			while(!findSelfName()) {
				try {
					Thread.sleep(1000);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
			//初始化心跳线程序列
			hbList = new ArrayList<HeartbeatThread>();
			//刷新服务器信息
			HeartbeatThread ele; //心跳线程元素
			//群组名元素序列
			List<String> groupList = BaseUtil.getMapKeyList(groupServerMap);
			//分组中的服务器序列
			SListMap<ServerInfoVO> groupServerList;
			ServerInfoVO siVO; //服务器信息容器
			for(String group:groupList) {
				groupServerList = groupServerMap.get(group);
				if(groupServerList!=null) {
					for(int i=0;i<groupServerList.size();i++) {
						siVO = groupServerList.get(i);
						if(localServerVO.disabledSendServerNameList.contains(siVO.name) || siVO.local) {
							//禁止发送心跳包到该服务器 和本机不需要心跳线程
							continue;
						}
			    		ele = new HeartbeatThread(siVO,i);
			    		ele.start();
			    		hbList.add(ele);
					}
				}
			}
			
	        /*
	         * 实践证明：没有起到实际作用，无用
	         * 启动心跳包防止阻塞线程
	        csThread = new CheckStiffThread();
	        csThread.start();
	        */
			
	        //启动token超时检测线程
	        ttot = new TokenTimeOutThread();
	        ttot.start();
		}
	}
	
	/**
	 * 检测token是否超时线程
	 * @author MBG
	 * 2019年7月9日
	 */
	private class TokenTimeOutThread extends Thread {
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public TokenTimeOutThread() {
			super("ClusterFilter-TokenTimeOutThread");
		}
		
		/**
		 * 覆盖方法
		 */
		@Override
        public void run() {
			while(true) {
				//获取全部Token
				List<String> tokenList = tokenMap.keys();
				long         now       = System.currentTimeMillis(); //当前时间
				Long         tokenTime;                              //token创建时间
				for(String token:tokenList) {
					//获取token创建时间
					tokenTime = tokenMap.get(token);
					if(tokenTime==null) {
						tokenMap.remove(token);
						msgMap.remove(token);
						continue;
					}
					if(tokenTime+60*60*1000<now) {
						//超时
						tokenMap.remove(token);
						msgMap.remove(token);
					}
				}
				try {
					Thread.sleep(5*60*1000); //休眠5分钟
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 接收心跳包超时检查线程
	 * @author MBG
	 * 2018年7月23日
	 */
	private class ReceiveTimeOutThread extends Thread {
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public ReceiveTimeOutThread() {
			super("ClusterFilter-ReceiveTimeOutThread");
		}
		
		
		/**
		 * 覆盖函数
		 */
		@Override
        public void run() {
			while(true) {
				try {
					Thread.sleep(1000);
				}catch(Exception e) {
					return;
				}
				long outTime = 5*60*1000;              //超时时间(5分钟）
				long now = System.currentTimeMillis(); //当前时间
				ServerInfoVO siVO; //服务器信息容器
				for(int i=0;i<serverMap.size();i++) {
					siVO = serverMap.get(i);
					if(siVO.validReceive && !siVO.local) {
						if(siVO.receiveTime+outTime<now) {
							//因为很久没收到目标服务器心跳，标记为无法收到消息
							siVO.validReceive = false;
						}
					}
				}
			}
		}
	}
	
	/**
	 * 心跳线程
	 * @author MBG
	 * 2016年11月19日
	 */
	private class HeartbeatThread extends Thread {
		
		//private long startTime = 0;  //刷新集群信息的开始时间 无用
		private ServerInfoVO siVO;   //服务器信息容器
		private int index;           //服务器索引
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public HeartbeatThread(ServerInfoVO siVO,int index) {
			super("ClusterFilter-HeartbeatThread-"+siVO.name+"@"+siVO.group+"-"+index);
			this.siVO = siVO;
			this.index = index;
		}
		
		/*
		 * 无用
		 * 
		 * 获取刷新集群信息的开始时间
		 * @return 刷新集群信息的开始时间
		 * 2019年5月11日
		 * @author MBG
		 *
		public long getStartTime() {
			return startTime;
		}
		*/
		
		/*
		 * 无用
		 * 获取对应的服务器信息线程
		 * @return 对应的服务器信息线程
		 * 2019年5月11日
		 * @author MBG
		 *
		public ServerInfoVO getVO() {
			return siVO;
		}
		*/
		
		/**
		 * 覆盖方法
		 */
		@Override
        public void run() {
			
			log("~~ClusterFilter The HeartbeatThread: Server:["+siVO.name+"] Group:["+siVO.group+"] Begin start...");
			
			//放入调用目标服务器超时时间
			ThreadSession.put("_httpcall_connect_time_out",String.valueOf(timeOut));
			
			//刷新服务器信息
			while(true) {
				//startTime = System.currentTimeMillis(); 无用
				try {
					Thread.sleep(1000);
				}catch(Exception e) {
					e.printStackTrace();
				}
				/*
				 * 在起线程时就已经过滤掉本机了，不用在这里判断
				if(siVO.local) {
					//就是本机，在启动后确认该信息为本机信息时，退出线程
					hbList.remove(this);
					return;
				}
				*/
				refreshCluster(siVO,index); //刷新服务器信息
				
				//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
				//if(siVO.invalidServer) {
				//	//发现该服务器为无效集群服务器，不再使用该服务器
				//	warning("\n\n********************************************************\nThis Cluster Server Is Invalid. So Not Used\n"+siVO+"\n********************************************************\n\n");
				//	return;
				//}
			}
		}
	}
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ClusterFilter() {
		super();
	}


	/**
	 * 覆盖方法
	 */
	@Override
	public int getIndex() {
		return 2;
	}
	
	/**
	 * 执行初始化
	 * 
     * 配置文件格式：
     * 
     * <cluster disabled="0" time_out="10000">
     *      <server name="E1" ip="127.0.0.1" url="http://localhost" disabled="0"/>
     * </cluster>
     * 
     * server 服务器是有优先顺序的，第一台有效的服务器为主服务器，一些标记为集群服务的程序只有主服务器才执行
	 * 
	 * 
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
    @SuppressWarnings("deprecation")
    @Override
    public void init(FilterExplorer fe, FilterConfig config) throws Exception {
      clusterMode = 0;
      // 先定义临时名字
      initTempServerName();

      groupServerMap.clear(); // 清空序列容器
      serverMap.clear();
      ipList.clear();
      allowIpList.clear();

      // 被允许访问请求的Ip信息序列（对外）
      List<String> allowIpInfoList = new ArrayList<String>();

      // 获取集群服务器信息
      IViewHandler clusterVh = px("cluster");

      // 是否禁用集群
      disabled = boo(clusterVh.a("disabled"));

      // 是否禁用集群远程调用负载均衡
      invokeLoadBalancing = !boo(clusterVh.a("disabledInvokeLoadBalancing"));

      // 反向触发调用服务等待返回值超时时间（毫秒）
      REVERSE_TRIGGER_TIME_OUT = sint(clusterVh.a("reverse_trigger_time_out"));
      if (REVERSE_TRIGGER_TIME_OUT < 1) {
        REVERSE_TRIGGER_TIME_OUT = 5 * 60 * 1000; // 默认5分钟
      }

      // 反向触发调用服务获取返回值间隔时间（毫秒）
      REVERSE_TRIGGER_INTERVAL_TIME = sint(clusterVh.a("reverse_trigger_interval_time"));
      if (REVERSE_TRIGGER_INTERVAL_TIME < 1) {
        REVERSE_TRIGGER_INTERVAL_TIME = 100; // 默认100毫秒
      }

      // 是否固定集群主服务器（固定后就不检查主服务器是否可以访问）
      fixedMaster = boo(clusterVh.a("fixed_master"));

      sl = (ScriptLoader) ((IBeanFactoryManager) _beanFactory).getNativeObject(ScriptLoader.class, this);

      // 从配置文件中获取超时时间
      int intValue = sint(clusterVh.a("time_out"));
      if (intValue > 0) {
        timeOut = intValue;
      }
      // 获取分组信息序列
      List<IViewHandler> groupList = clusterVh.ocnn("group");
      // 服务器信息序列
      List<IViewHandler> serverList;
      Map<String, String> groupData; // 分组信息数据容器
      for (IViewHandler ele : groupList) {
        groupData = ele.am();
        serverList = ele.ocnn("server");
        for (int i = 0; i < serverList.size(); i++) {
          // 设置服务器信息
          setServer(serverList.get(i), i, groupData, allowIpInfoList);
        }
      }
      // 服务器信息序列
      serverList = clusterVh.ocnn("server");
      IViewHandler vh; // 服务器信息元素
      for (int i = 0; i < serverList.size(); i++) {
        vh = serverList.get(i);

        // 设置服务器信息
        setServer(vh, i, null, allowIpInfoList);
      }
      // 附加的IP地址
      String[] otherIps = BaseUtil.split(clusterVh.a("addition_ip"), ",");
      if (otherIps != null && otherIps.length > 0) {
        for (int i = 0; i < otherIps.length; i++) {
          if (otherIps[i] == null || allowIpInfoList.contains(otherIps[i])) {
            continue;
          }
          allowIpInfoList.add(otherIps[i]);
        }
      }
      /* 处理外部允许访问的ip信息 */
      String[] ipInfos; // 拆分的ip信息数组
      String[] allowInfos; // 整理后的ip信息数组
      for (String ip : allowIpInfoList) {
        ipInfos = BaseUtil.split(ip, ".");
        if (ipInfos == null || ipInfos.length < 1) {
          continue;
        }
        allowInfos = new String[4];

        if (ipInfos.length < 2) {
          allowInfos[0] = str(ipInfos[0]);
          allowInfos[1] = "*";
          allowInfos[2] = "*";
          allowInfos[3] = "*";
        } else if (ipInfos.length < 3) {
          allowInfos[0] = str(ipInfos[0]);
          allowInfos[1] = str(ipInfos[1]);
          allowInfos[2] = "*";
          allowInfos[3] = "*";
        } else if (ipInfos.length < 4) {
          allowInfos[0] = str(ipInfos[0]);
          allowInfos[1] = str(ipInfos[1]);
          allowInfos[2] = str(ipInfos[2]);
          allowInfos[3] = "*";
        } else {
          allowInfos[0] = str(ipInfos[0]);
          allowInfos[1] = str(ipInfos[1]);
          allowInfos[2] = str(ipInfos[2]);
          allowInfos[3] = str(ipInfos[3]);
        }
        allowIpList.add(allowInfos);
      }
      if (serverList.size() < 1) {
        disabled = true;
      }
      if (disabled) {
        clusterMode = 2;
        return;
      }

      rcf = bean(RegistCenterFilter.class);

      // 启动初始化线程
      (new InitThread()).start();
      // 启动检测接收消息超时线程
      rtoThread = new ReceiveTimeOutThread();
      rtoThread.start();

      log.startLog("[cluster] Begin Find SelfServerName From ClusterConfig Info.....");
    }
	
	/**
	 * 设置服务器信息
	 * @param vh               服务器信息节点
	 * @param index            服务器索引
	 * @param groupData        服务器分组数据
	 * @param allowIpInfoList  限制IP信息数据序列
	 * 2018年7月24日
	 * @author MBG
	 */
	private void setServer(
			 IViewHandler vh
			,int index
			,Map<String,String> groupData
			,List<String> allowIpInfoList) {
		//构建服务器信息类
		ServerInfoVO siVO = new ServerInfoVO();
    	siVO.setParameterMap(vh.am());
    	//不能设置循环所谓为服务器索引，因为不同群组的第一台服务器的索引都为0
    	//siVO.serverIndex = i;
    	if(siVO.disabled) {
    		return;
    	}
    	siVO.group = vh.a("group");
    	if(siVO.group.length()<1 && groupData!=null) {
    		siVO.group = str(groupData.get("name"));
    	}
    	
    	siVO.name = vh.a("name");
    	if(siVO.name.length()<1) {
    		siVO.name = siVO.group+(index+1);
    	}
    	
    	//获取是否存在前置反向代理服务器做负载均衡
    	String agenBalance = vh.a("agent_balance");
    	if(agenBalance.length()<1 && groupData!=null) {
    		agenBalance = str(groupData.get("agent_balance"));
    	}
    	
    	//设置禁止发心跳包到目标服务器的服务器名
    	siVO.disabledSendServerNameList.addAll(BaseUtil.splitToList(vh.a("dis_send"),",",";","，","；"));
    	
    	//取消该变量值，无用
    	//siVO.agentBalance = boo(agenBalance);
    	
    	//允许访问的IP地址序列
    	List<String> allowIps = BaseUtil.splitToList(vh.a("allow"),",");
    	for(String ip:allowIps) {
        	if(!allowIpInfoList.contains(ip)) {
        		allowIpInfoList.add(ip);
        	}
    	}
    	
    	//获取指定分组中的服务器信息序列
    	SListMap<ServerInfoVO> sListMap = groupServerMap.get(siVO.group);
    	if(sListMap==null) {
    		sListMap = new SListMap<ServerInfoVO>();
    		groupServerMap.put(siVO.group,sListMap);
    	}
    	siVO.serverIndex = sListMap.size(); //放入当前分组服务器索引
    	sListMap.put(siVO.name,siVO);
    	
    	serverMap.put(siVO.name,siVO);
    	ipList.add(siVO.ip);
    	if(!allowIpInfoList.contains(siVO.ip)) {
    		allowIpInfoList.add(siVO.ip);
    	}
	}
	
    /**
     * 注销方法
     * 
     * 2016年11月20日
     * @author MBG
     */
    @SuppressWarnings("deprecation")
	public void destroy() {
    	if(hbList==null || hbList.size()<1) {
        	for(HeartbeatThread ele:hbList) {
            	try {
            		ele.stop();
            	}catch(Exception e) {}
            	ele = null;
        	}
        	hbList.clear();
        	hbList = null;
    	}
    	try {
    		rtoThread.stop();
    	}catch(Exception e) {}
    	rtoThread = null;
    }
    
    /**
     * 查找集群服务器信息中，哪条服务器信息是本机信息
     * 
     * 注意：不能在这里打日志，会很多。
     * 
     * @return 是否找到
     * 2016年11月18日
     * @author MBG
     */
    private boolean findSelfName() {
    	
    	log("\n******************************************\nCluster Begin Find Self ServerName\n");
    	
    	ServerInfoVO sVO; //服务器信息类
    	//请求头容器
    	Map<String,String> headerMap = new HashMap<String,String>();
    	headerMap.put("_cluster_mode_","1");      //获取请求状态，返回目标服务器名
    	headerMap.put("_cluster_key","_starting_"); //标记为刚启动还不知道自己叫啥名
    	IViewHandler res; //返回值Json对象
    	
    	//群组索引容器
    	Map<String,Integer> groupIndexMap = new HashMap<String,Integer>();
    	for(int i=0;i<serverMap.size();i++) {
    		sVO = serverMap.get(i);
    		try {
    			log("=====ClusterFilter: name:["+sVO.name+"] group["+sVO.group+"] Call:["+sVO.url+sVO.context+CLUSTER_ACTION+"]");
    			res = HttpCall.xCall(sVO.url+sVO.context+CLUSTER_ACTION,null,1,headerMap);
    			log("=====ClusterFilter Res:"+res);
    		}catch(Exception e) {
    			if(fixedMaster) {
    				//如果是固定主服务器，即使服务器访问不通，也要记录索引
    	    		//相同群组索引加1
    	    		groupIndexMap.put(sVO.group,sint(groupIndexMap.get(sVO.name))+1);
    			}
    			continue;
    		}
    		if(localServerTempName.equals(res.fnn("name").nt())) {
    			//发现本机
    			sVO.local        = true;      //标记当前信息为本机信息
    			sVO.startTime    = SDate.getTs(ServiceInfo.startTime); //设置本机启动时间
    			localServerVO    = sVO;
    			isTempServerName = false;
    			clusterMode      = 1;         //标记为集群模式
    			
    			if(!fixedMaster && sint(groupIndexMap.get(sVO.name))==0) {
    				//如果当前服务器就是集群中第一个服务器（第一个配置信息）
    				//并且已经设置了固定主服务器
    				//直接标记当前服务器为主服务器
    				sVO.master = true;
    			}
    			
    			//设置序列号生成器：序列号头部
    			try {
    				FSN.setIndex(String.valueOf(localServerVO.serverIndex));
    			}catch(Exception e) {
    				e.printStackTrace();
    			}
    			
    			//为日志处理类配置集群支持功能
    			try {
        			XLogFilter xlf = bean(XLogFilter.class);
        			xlf.init(this);
    			}catch(Exception e) {
    				e.printStackTrace();
    			}
    			
    	    	//为需要本机服务器名的程序做初始化
    	    	for(ITriggerInit ele:initBeanList) {
    	    		try {
    	    			ele.init(this);
    	    		}catch(Exception e) {
    	    			e.printStackTrace();
    	    		}
    	    	}
    	    	for(IClusterTrigger ele:triggerBeans.values()) {
    	    		try {
    	    			ele.clusterInited(this);
    	    		}catch(Exception e) {
    	    			e.printStackTrace();
    	    		}
    	    	}
    	    	
    			log("\n******************************************\n"
    		    + "       ** Cluster Mode Enabled **\n\n"
    		    + "Current Server Infomation:\n\n"
    			+ " Server Name:["+sVO.name+"] \n          IP:["+sVO.ip+"] \n         URL:["+sVO.url+"] context:["+sVO.context+"] \n\n******************************************\n\n");
    			
    			findMeNameOK = true;
    			
    			return true;
    		}
    		//相同群组索引加1
    		groupIndexMap.put(sVO.group,sint(groupIndexMap.get(sVO.name))+1);
    	}
    	return false;
    }
    
    /**
     * 刷新指定服务器状态信息
     * @param sVO 服务器信息类
     * @param serverIndex 服务器索引
     * 2017年4月21日
     * @author MBG
     */
    private void refreshCluster(ServerInfoVO sVO,int serverIndex) {
    	//请求头容器
    	Map<String,String> headerMap = new HashMap<String,String>();
    	headerMap.put("_cluster_mode_","1"); //获取请求状态，返回目标服务器名
    	
    	//计算服务器同步时间时使用到的，估算发送消息到目标服务器这段时间
    	//当前方法是这样计算的：目标主服务器返回响应时的时间，到当前服务器接收到
    	//                    目标主服务器信息的这段时间是 当前服务器发送消息，到
    	//                    接收到目标主服务器的时间的一半
    	long callDelayTime;
    	INodeHandler res; //返回值XML对象
    	int findMasterIndex = -1; //发现的主服务器索引    -1没发现
    	boolean objMasterServer = false; //对方认为自己是主服务器
    	
    	//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
		//if(sVO.disabled || sVO.invalidServer) {
    	if(sVO.disabled) {
			//该服务器正在关禁闭，剥其夺政治权利
			return;
		}
    	
		if(localServerVO.name.equals(sVO.name)) {
			//没必要调用自己，并且自己肯定是有效的
			sVO.validSend = true;
			sVO.validReceive = true;
			return;
		}
		callDelayTime = System.currentTimeMillis();
		//放入集群名
		headerMap.put("_cluster_key",localServerVO.name);
		
		//有时群组跟群组之间是单向通信的，需要一方告诉另外一方自己是不是主服务器
		headerMap.put("_cluster_master",localServerVO.master?"1":"0"); //是否为主服务器
		try {
			res = HttpCall.xCall(sVO.url+sVO.context+CLUSTER_ACTION,null,1,headerMap);
		}catch(HttpException he) {
			if(he.getStatusCode()==404) {
				
				//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
				//sVO.invalidServer = true;
				
				/*
				 * 不能禁用掉，假如前台可以访问后台，但是后台无法访问前台。后台服务器宕机后，前台无法访问后台，于是
				 * 把后台服务器disabled，但是后台服务器恢复以后，前台服务器就再也不会重新访问后台了。
				 */
				//sVO.disabled = true;
				return;
			}
			sVO.validSend = false; //无效
			return;
		}catch(Exception e) {
			sVO.validSend = false; //无效
			return;
		}
		//res.fnn("name") 兼容老版本，没有status节点
		//无效
		sVO.validSend   = boo(res.fnn("status").nt()) || res.fnn("name").nt().length() > 0;                                       //有效
		
		sVO.msg             = res.fnn("msg").nt();                        //目标服务器错误信息
		sVO.sendTime        = System.currentTimeMillis();                 //刷新时间
		sVO.invokeCount     = sint(res.fnn("invoke_count").nt());         //服务器被远程调用次数
		sVO.startTime       = res.fnn("start_time").nt();                 //服务器启动时间
		sVO.runTime         = res.fnn("run_time").nt();                   //服务器运行时长
		sVO.invokeClearFlag = sint(res.fnn("invoke_clear_flag").nt());    //服务器远程调用次数清除标识
		
		if(!localServerVO.group.equals(sVO.group)) {
			//如果这台服务器并不是同一组的，就不继续同步服务器时间，检测主服务器
			
			sVO.master = boo(res.fnn("master").nt()); //获取对方群组中，那台服务器是否为主服务器
			
			checkRecall(res,sVO); //检查是否有反向触发调用请求
			
			if(!sVO.triggerFirstSend) {
				//执行首次成功发送心跳包触发事件
				sVO.triggerFirstSend = true;
				for(IClusterTrigger ele:triggerBeans.values()) {
					ele.validSended(sVO);
				}
			}
			return;
		}
		
		boolean hasRecall = checkRecall(res,sVO); //检查是否有反向触发调用请求
		
		callDelayTime = (System.currentTimeMillis()-callDelayTime)/2;
		//对方认为自己是主服务器
		objMasterServer = boo(res.fnn("master").nt());
		if(objMasterServer && localServerVO.serverIndex>serverIndex) {
			//如果对方认为自己是主服务器，并且对方在集群的排名在自己之前
			
			if(findMasterIndex<0) {
				findMasterIndex = serverIndex;
			}
			//如果目前本机不是主服务器
			//有可能做了临时主服务器，可人家真正主服务器又原地复活了
    		if(!localServerVO.master) {
    			//如果当前服务器不是主服务器
				//跟主服务器校对服务器时间
				long masterTime = lon(res.fnn("time").nt());
				if(!hasRecall && masterTime>0) {
					//如果返回报文中包含了反向触发信息，因为信息量大，估算的服务器时间误差就会很大
					//故放弃本次同步服务器时间
					amendTime = masterTime-(System.currentTimeMillis()-callDelayTime);
				}
				masterServerName = sVO.name;
    		}
		}
		//else  对方认为自己是主服务器，但是在集群排名中比当前服务器落后
		//      这说明当前服务器刚启动，对方服务器之前做了临时的主服务器
		//      对方服务器刷新数据找到当前服务器后，就不再做主服务器了
		
		if(!sVO.triggerFirstSend) {
			//执行首次成功发送心跳包触发事件
			sVO.triggerFirstSend = true;
			for(IClusterTrigger ele:triggerBeans.values()) {
				ele.validSended(sVO);
			}
		}
		
		if(localServerVO.master && localServerVO.serverIndex==0) {
			//当前就是主服务器，准转正常
			return;
		}
		if(localServerVO.serverIndex==0) {
			if(objMasterServer) {
				sVO.master = false;
			}
			localServerVO.master = true;
			//老子又回来啦
			return;
		}
		if(!fixedMaster) {
			//如果没有固定主服务器
			if(findMasterIndex<0) {
				//完蛋了，本机不是主服务器，也没发现主服务器，主服务器跪了
				
				//检查是否有比本机更适合做主服务器的
				for(int i=localServerVO.serverIndex-1;i>-1;i--) {
					sVO = serverMap.get(i);
					if(!sVO.disabled && sVO.validSend) {
						//原来还有比我个高的，天塌了不用顶
						return;
					}
					//次奥，继续找
				}
				if(objMasterServer) {
					sVO.master = false;
				}
				//法克，只有自己最适合做主服务器了，当仁不让
				localServerVO.master = true;
				return;
			}
			
			//已经发现主服务器
			
			//看看发现的主服务器的索引值是否比自己更靠前
			if(findMasterIndex<localServerVO.serverIndex) {
				//有更适合做主服务器的了，自己可以歇着了
				localServerVO.master = false;
				return;
			}
			//别犹豫，自己就是有资格做主服务器，不管目前是不是主服务器
		}
		return;
    }
    
    
    /**
     * 检查返回信息中是否包含反向触发调用信息
     //* @param nh    返回信息包
     * @param siVO  目标服务器信息
     //* @param req   页面请求
     * @param res  页面反馈
     * @return      true包含反向触发调用信息
     * 2017年4月22日
     * @author MBG
     */
    private boolean checkRecall(
    		IViewHandler res,ServerInfoVO siVO) {
    	//尝试获取回调token
    	String token = res.fnn("call_token").nt();
    	if(token.length()<1) {
    		return false;
    	}
    	//尝试获取调用目标脚本主键
    	String scriptId = res.fnn("call_script").nt();
    	if(scriptId.length()<1) {
    		//没有反向触发调用信息
    		return false;
    	}
    	
		//解析出传入参数对象（传入参数通常不应该是异常类）
    	Object para = null;
    	try {
    		//传入参数
    		IViewHandler paraVh = res.fnn("call_param").fnn("v");
    		debug("[cluster] In Reverse Trigger Invoke Event Script:["+scriptId+"] MsgContent:\n"+res);
    		para = getSerializer().unserialize(paraVh,null,null,false);
    	}catch(Exception e) {
    		e.printStackTrace();
    		return true;
    	}
    	
		//---------------累加调用计数器---------------
		if(invokeCount==Integer.MAX_VALUE) {
			invokeCount = 0;
			if(invokeClearFlag==Integer.MAX_VALUE) {
				invokeClearFlag = 0;
			}else {
				invokeClearFlag++;
			}
		}else {
			invokeCount++;
		}
		//---------------累加调用计数器---------------
		
    	//调用类型 0普通  1只调用主机  2广播调用
    	int callType = sint(res.fnn("call_type").nt());
    	Object resObj = null; //返回值
    	if(callType==1) {
    		if(localServerVO.master) {
    			//本机就是主机
    			
        		//调用本地方法并获取返回值
        		resObj = callSelf(scriptId,para);
    		}else {
    			//不可能出现这种情况，集群调用都都是调用目标群组主机
    			
				//获取当前群组的主服务器信息
				ServerInfoVO msiVO = getMasterServer(localServerVO.group);
				if(msiVO==null) {
					//没找到目标主机（这更离谱，不可能）
					resObj = callSelf(scriptId,para);
				}else {
					try {
						if(localServerVO.name.equals(msiVO.name)) {
							resObj = callSelf(scriptId,para);
						}else {
							resObj = call(msiVO.name,scriptId,para,0);
						}
					}catch(Exception e) {
						e.printStackTrace();
						resObj = e;
					}
				}
    		}
    	}else if(callType==2) {
    		//广播调用
			try {
				//先调用当前服务器
				callSelf(scriptId,para);
				//广播调用其它服务器（不包含当前服务器）
				callAll(scriptId,para);
			}catch(Exception e) {
				e.printStackTrace();
			}
    	}else {
    		//调用本地方法并获取返回值
    		resObj = callSelf(scriptId,para);
    	}
    	if(boo(res.fnn("no_return_value").nt())) {
    		//请求端提出不需要返回值
    		return true;
    	}
    	//请求头容器
    	Map<String,String> headerMap = new HashMap<String,String>();
    	headerMap.put("_cluster_mode_","3"); //获取请求状态，返回值
		//放入集群名
		headerMap.put("_cluster_recall_token",token);
		
		//发起服务器名
		String fromServer = res.fnn("src_server").nt();
		
		//标记发起服务器信息，因为反向触发调用，外部服务器主动调用内网Nginx，通过Nginx调用其内部并行集群中指定一台服务器
		//是没发做到的，因为通过Nginx做了负载均衡，调用到目标服务器是随机的，所以需要传入该值，如果目标服务器不是发起的
		//服务器，就需要接收服务器将收到的信息转发给发起服务器
		headerMap.put("_cluster_from_server_",fromServer);
		
		//构建返回信息报文
		StringBuffer reSbf = new StringBuffer();
		reSbf
			.append("<?xml version=\"1.0\" encoding='UTF-8'?><root from=\"")
			.append(fromServer)
			.append("\" receive=\"")
			.append(localServerVO.name)
			.append("\"><status>1</status>")
			.append(getSerializer().serialize(resObj))
			.append("</root>");
		debug("[cluster] After Reverse Trigger From Server:["+fromServer+"] Invoked The Script:["+scriptId+"] Msg:\n"+reSbf);
		try {
			res = HttpCall.xCall(siVO.url()+CLUSTER_ACTION,reSbf.toString(),1,headerMap);
		}catch(Exception e) {
			siVO.validSend = false; //无效
		}
    	return true;
    }

    /**
     * 定义临时服务器名
     * 2016年11月18日
     * @author MBG
     */
    private void initTempServerName() {
    	localServerTempName = "PX-"+System.currentTimeMillis()+"-"+StringUtil.random(5);
    	isTempServerName = true;
    }

    /**
     * 覆盖方法
     */
	@Override
	public String getFilterActionExtName() {
		return "ha";
	}

	/**
	 * 覆盖方法
	 */
	@Override
	public boolean doFilter(IRequest req, IResponse resp) throws Exception {
		if(!req.getServletPath().equals(CLUSTER_ACTION)) {
			return false;
		}
		if(disabled) {
			return true;
		}
		//构建返回值
		StringBuffer reSbf = new StringBuffer();
		if(!allow(req)) {
			warning("ClusterFilter doFilter The Ip:["+req.cip()+"] Not Found From Config File");
			reSbf
				.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
				.append("<root>")
				.append("<name>").append(localServerTempName).append("</name>")
				.append("<status>0</status><msg>access</msg>")
				.append("</root>");
			out(resp,reSbf.toString());
			return true;
		}
		//集群处理模式
        int mode = sint(req.getHeader("_cluster_mode_"));
		if(mode==1 || mode==0) {
			//返回当前服务器名字，服务器时间
			if(isTempServerName) {
				//只是为了寻找自己的名字
				reSbf
				.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
				.append("<root>")
				.append("<name>").append(localServerTempName).append("</name>")
				.append("<status>1</status>")
				.append("</root>");
				out(resp,reSbf.toString());
				return true;
			}
			reSbf
				.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
				.append("<root>")
				.append("<group>").append(localServerVO.group).append("</group>")
				.append("<name>").append(localServerVO.name).append("</name>")
				.append("<master>").append(localServerVO.master?"1":"0").append("</master>");
			//集群服务器名
			String remoteName = str(mode==0?req.getUrlParameter("_cluster_key"):str(req.getHeader("_cluster_key")));
			boolean showLog = false; //是否显示反向触发调用提交参数信息
			if(!(mode==1 && remoteName.equals("_starting_"))) {
				//除去寻找本机服务器名的请求
				if(remoteName.length()>0) {
					//获取指定的服务器信息
					ServerInfoVO remoteSiVO = serverMap.get(remoteName);
					if(remoteSiVO==null) {
						if(localServerVO!=null && localServerVO.anonymous) {
							//允许未登记服务器加入集群
							remoteSiVO                    = new ServerInfoVO();
							remoteSiVO.group              = localServerVO.group;
							remoteSiVO.name               = remoteName;
							remoteSiVO.anonymousClient    = true;
							remoteSiVO.disabled           = false;
							//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
							//remoteSiVO.invalidServer    = false;
							remoteSiVO.ip                 = req.getRemoteAddr();
							remoteSiVO.master             = false;
							remoteSiVO.validSend          = false;
							serverMap.put(remoteName,remoteSiVO);
							remoteSiVO.triggerFristRecive = true;
							for(IClusterTrigger ele:triggerBeans.values()) {
								ele.validReceived(remoteSiVO);
							}
						}
					}
					if(remoteSiVO==null) {
						reSbf.append("<status>0</status><msg>Server Name ["+remoteName+"] Has Not Set</msg>");
						warning("ClusterFilter doFilter The Key:[_cluster_key]["+remoteName+"] Not Found From Config File");
					}else {
						remoteSiVO.disabled = false;    //解除禁用。因为有时目标服务器宕机无法被访问到，其他服务器会将这台服务器disabled，直到再次接收到这台服务器消息
						remoteSiVO.validReceive = true; //标记响应请求
						remoteSiVO.receiveTime = System.currentTimeMillis();
						
						if(!remoteSiVO.validSend) {
							//如果无法主动访问来源服务器（单向访问网络环境）
							remoteSiVO.master = boo(req.getHeader("_cluster_master"));
						}
						//获取准备回复目标服务器的消息序列
						RollIterator<Object[]> rit = msgMap.get(remoteName);
						if(rit!=null && rit.hasNext()) {
							//存在回复消息   0token  1发起服务器主键 2发起服务器所在群组  3调用脚本主键  4条用类型  5是否需要返回值  6传入参数  
							Object[] msgs = rit.next();
							if(msgs!=null) {
                reSbf
                  .append("<call_token>").append(msgs[0]).append("</call_token>")
                  .append("<src_server>").append(msgs[1]).append("</src_server>")
                  .append("<src_group>").append(msgs[2]).append("</src_group>")
                  .append("<call_script>").append(msgs[3]).append("</call_script>")
                  .append("<call_type>").append(msgs[4]).append("</call_type>")
                  .append("<no_return_value>").append(((Boolean)msgs[5])?"1":"0").append("</no_return_value>")
                  .append("<call_param>").append(getSerializer().serialize(msgs[6])).append("</call_param>");
								showLog = true;
							}
						}
						reSbf
							.append("<start_time>").append(SDate.getDateFromMillisecond(ServiceInfo.startTime)).append("</start_time>")
							.append("<run_time>").append(SDate.runTimeInfo(System.currentTimeMillis()-ServiceInfo.startTime)).append("</run_time>")
							.append("<time>").append(time()).append("</time>")
							.append("<invoke_count>").append(invokeCount).append("</invoke_count>")
							.append("<invoke_clear_flag>").append(invokeClearFlag).append("</invoke_clear_flag>")
							.append("<status>1</status>");
						
						//执行首次接收心跳包触发事件
						if(!remoteSiVO.triggerFristRecive) {
							remoteSiVO.triggerFristRecive = true;
							for(IClusterTrigger ele:triggerBeans.values()) {
								ele.validReceived(remoteSiVO);
							}
						}
					}
				}else {
					reSbf.append("<status>0</status><msg>ServerNameIsEmpty</msg>");
				}
			}
			reSbf.append("</root>");
			
			if(showLog) {
				debug("[cluster] ["+remoteName+"] Call Reverse Trigger Invoke Xml Message:\n"+reSbf);
			}
			out(resp,reSbf.toString());
			return true;
		}else if(mode==2) {
			//内部集群调用目标脚本
			executeEvent(req,resp); 
            return true;
		}else if(mode==3) {
			//反向触发调用服务返回值处理
			
			//发起请求的服务器名
			String fromServer = str(req.getHeader("_cluster_from_server_"));
			//token
			String token = str(req.getHeader("_cluster_recall_token"));
			if(token.length()<1) {
				return true;
			}
			if(fromServer.equals(localServerVO.name)) {
				//确定是本机发起的反向触发
				//获取返回信息
				Object[] resInfos = getEventPara(req,resp);
				debug("[cluster] Reverse Invoke Remote Param:\n"+DebugUtil.objectInfo(resInfos[3]));
				//将返回值放入对照容器中
				fixTokenValue(token,resInfos[3],true);
				return true;
			}
			
			debug("[cluster] Recive The Trigger Message,LocalServer:["+localServerVO.name+"] Need ReCall Server:["+fromServer+"]");
			
			//外部服务器响应反向触发请求后，将返回信息通过Nginx负载均衡主动调用到内部服务器，但实际上
			//接收外部服务器消息的内部服务器，并不是那台发起反向触发的内部服务器（因为负债均衡的原因
			//和内部服务器无法主动调用外部服务器的策略，只能由外部服务器主动调用内部服务器），并且经常
			//会遇到这种情况，所以需要接收到反向触发返回信息的服务器，将信息转发给发起服务器
			
			//需要转发到的服务器信息
			ServerInfoVO siVO = getServerInfo(fromServer);
			if(siVO==null || siVO.disabled || !siVO.validSend) {
				warning("[cluster] Recive The Trigger Message,LocalServer:["
			         +localServerVO.name+"] Need ReCall Server:["
				     +fromServer+"] Cant't Call The Target Server:\n"+siVO,null);
				return true;
			}
			//请求头容器
	    	Map<String,String> headerMap = new HashMap<String,String>();
	    	headerMap.put("_cluster_mode_","3");          //获取请求状态，返回值
			headerMap.put("_cluster_recall_token",token); //放入集群名
			
			//标记发起服务器信息，因为反向触发调用，外部服务器主动调用内网Nginx，通过Nginx调用其内部并行集群中指定一台服务器
			//是没发做到的，因为通过Nginx做了负载均衡，调用到目标服务器是随机的，所以需要传入该值，如果目标服务器不是发起的
			//服务器，就需要接收服务器将收到的信息转发给发起服务器
			headerMap.put("_cluster_from_server_",fromServer);
			
			//编码格式
			String enc = req.getHeader("encoding");
			if(enc==null || enc.length()<1) {
				enc = req.getCharacterEncoding();
				if(enc==null || enc.length()<1) {
					enc = "UTF-8";
				}
			}
			
			String content; //接收到的信息字符串
			//从头信息中获取，或者从url参数中获取是否做Base64解码
			if(boo(req.getHeader("base64_enc")) 
					|| boo(req.getUrlParameter("base64_enc"))) {
				content = FileCopyTools.copyToString(req.getInputStream());
				if(content.startsWith("@b64@")) {
					content = content.substring(5);
				}
				//读取完整的内容并做Base64解密
				content = Base64.base64Decode(content,enc);
			}else {
				//直接获取读入流
				content = FileCopyTools.copyToString(req.getInputStream(),enc);
			}
			debug("[cluster] LocalServer:["+localServerVO.name+"] Need ReCall Server:["+fromServer+"] \n"+content);
			try {
				HttpCall.xCall(siVO.url()+CLUSTER_ACTION,content,1,headerMap);
			}catch(Exception e) {
				siVO.validSend = false; //无效
			}
		}else if(mode==6) {
			//路由搜索信息
			if(localServerVO==null) {
				//服务器还没准备好，不做任何处理
				return true;
			}
			//目标服务器名
			String objName = req.getHeader("_cluster_route_obj_");
			if(objName==null || objName.length()<1) {
				resp.setHeader("_cluster_route_status_","0");
				return true;
			}
			//发起方服务器名
			String caller = req.getHeader("_cluster_route_caller_");
			if(caller==null || caller.length()<1) {
				resp.setHeader("_cluster_route_status_","0");
				return true;
			}
			//获取目标服务器配置信息
			ServerInfoVO siVO = getExecutableServer(
					objName,caller,BaseUtil.splitToList(req.getHeader("_cluster_route_groups_"),","),null);
			if(siVO==null) {
				resp.setHeader("_cluster_route_status_","0");
			}else {
				resp.setHeader("_cluster_route_status_","1");
				resp.setHeader("_cluster_route_level_",String.valueOf(siVO.routeLevel+1));
			}
			return true;
		}else if(mode==7) {
			//路由代理中转
			
			//目标服务器名
			String objName = req.getHeader("_cluster_route_obj_");
			if(objName==null || objName.length()<1) {
				resp.sendError(404,"Not Find The Object Route ServerName");
				return true;
			}
			//发起方服务器名
			String caller = req.getHeader("_cluster_route_caller_");
			if(caller==null || caller.length()<1) {
				resp.setHeader("_cluster_route_status_","0");
				return true;
			}
			//构建报文头对象
			Map<String,String> headerMap = new HashMap<String,String>();
			
			//获取目标服务器配置信息
			ServerInfoVO siVO = getExecutableServer(
					objName,caller,BaseUtil.splitToList(req.getHeader("_cluster_route_groups_"),","),headerMap);
			if(siVO==null) {
				resp.sendError(404,"Can't Call The Object Server:["+objName+"] ThisServer:["+name()+"] Caller:["+caller+"]");
				return true;
			}
			if(siVO.local) {
				//响应本地方法调用
				String triggerKey = req.getHeader("cluster_trigger_key");
				if(triggerKey!=null && triggerKey.length()>0) {
					//发起端服务器信息类
					String srcServer = req.getHeader("_cluster_route_src_");
					if(srcServer==null || srcServer.length()<1) {
						srcServer = str(req.getHeader("_cluster_key"));
					}
					//获取来源服务器信息
					ServerInfoVO srcVO = getServerInfo(srcServer);
					if(srcVO==null) {
						resp.sendError(404,"Not't Find The Source Server:["+srcServer+"]");
						return true;
					}
					//响应内部触发事件
					executeTrigger(triggerKey,srcVO,req,resp);
				}else {
					executeEvent(req,resp); 
				}
				return true;
			}
			//调用模式
			int clusterMode = sint(headerMap.get("_cluster_mode_"));
			if(clusterMode==0) {
				debug("[cluster] Begin By Route Call Server:["+siVO.name+"] Group:["+siVO.group+"]");
				headerMap.put("_cluster_mode_","2");
				headerMap.put("_cluster_call_",localServerVO.name);
			}else if(clusterMode==7) {
				//继续路由
				debug("[cluster] Begin Route Call Source:["
						+headerMap.get("_cluster_route_src_")+"] FinalTarget:["
						+headerMap.get("_cluster_route_obj_")+"] RouteLevel:["
						+siVO.routeLevel+"] ThisServer:["+siVO.name+"]");
			}
			try {
				post(siVO,CLUSTER_ACTION,headerMap,req,resp,null,null,null);
			}catch(Exception e) {
				e.printStackTrace();
			}
			return true;
		}else if(mode==8) {
			//触发调用 （支持路由代理中转，在上面）
			//触发主键
			String triggerKey = req.getHeader("cluster_trigger_key");
			if(triggerKey==null || triggerKey.length()<1) {
				resp.sendError(404,"Not Find The TriggerKey:["+req.getRemoteAddr()+"]");
			}else {
				//发起端服务器信息类
				String srcServer = req.getHeader("_cluster_route_src_");
				if(srcServer==null || srcServer.length()<1) {
					srcServer = str(req.getHeader("_cluster_key"));
				}
				//获取来源服务器信息
				ServerInfoVO srcVO = getServerInfo(srcServer);
				if(srcVO==null) {
					resp.sendError(404,"Not't Find The Source Server:["+srcServer+"]");
					return true;
				}
				//响应触发事件
				executeTrigger(triggerKey,srcVO,req,resp);
			}
			return true;
		}
		
		
		
//该模式已废弃，废弃原因请看类文件顶部注释
//		}else if(mode==4) {
//			//返回当前服务器是否可以联通目标服务器
//			//获取当前服务器连接指定目标服务器的信息
//			
//			//获取目标服务器名
//			String tarName  = str(req.getHeader("_cluster_target_name"));  
//			ServerInfoVO siVO = null; //目标服务器信息
//			if(groupServerMap.containsKey(tarName)) {
//				//传过来的是目标群组名
//				List<ServerInfoVO> sList = getServerInfoList(tarName);
//				for(ServerInfoVO ele:sList) {
//					//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
//					//if(ele==null || ele.disabled || !ele.invalidServer || !(ele.validReceive || ele.validSend)) {
//					if(ele==null || ele.disabled || !(ele.validReceive || ele.validSend)) {
//						continue;
//					}
//					if(siVO==null || (siVO.invokeCount>ele.invokeClearFlag && siVO.invokeClearFlag>=ele.invokeClearFlag)) {
//						siVO = ele;
//					}
//				}
//			}else {
//				//传过来的是服务器主键
//				siVO = getServerInfo(tarName); //获取目标服务器信息
//				//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
//				//if(siVO==null || siVO.disabled || !siVO.invalidServer || !(siVO.validReceive || siVO.validSend)) {
//				if(siVO==null || siVO.disabled || !(siVO.validReceive || siVO.validSend)) {
//					siVO = null;
//				}
//			}
//			reSbf
//				.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
//				.append("<root>");
//			
//			if(siVO==null) {
//				reSbf.append("<status>0</status>");
//			}else {
//				reSbf.append("<status>1</status>");
//			}
//			reSbf.append("</root>");
//			
//			out(resp,reSbf.toString());
//		}
			
// 该模式已废弃，具体废弃原因请看类文件顶部注释
//		}else if(mode==5) {
//			//同集群服务器中，委托调用目标服务器
//			
//			//获取调用目标脚本主键
//			String scriptId = str(req.getHeader("_cluster_target_script"));
//			//获取目标服务器名
//			String tarName  = str(req.getHeader("_cluster_target_name"));  
//			ServerInfoVO siVO = null; //目标服务器信息
//			if(groupServerMap.containsKey(tarName)) {
//				//传过来的是目标群组名
//				List<ServerInfoVO> sList = getServerInfoList(tarName);
//				for(ServerInfoVO ele:sList) {
//					//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
//					//if(ele==null || ele.disabled || !ele.invalidServer || !(ele.validReceive || ele.validSend)) {
//					if(ele==null || ele.disabled || !(ele.validReceive || ele.validSend)) {
//						continue;
//					}
//					if(siVO==null || (siVO.invokeCount>ele.invokeClearFlag && siVO.invokeClearFlag>=ele.invokeClearFlag)) {
//						siVO = ele;
//					}
//				}
//			}else {
//				//传过来的是服务器主键
//				siVO = getServerInfo(tarName); //获取目标服务器信息
//				//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
//				//if(siVO==null || siVO.disabled || !siVO.invalidServer || !(siVO.validReceive || siVO.validSend)) {
//				if(siVO==null || siVO.disabled || !(siVO.validReceive || siVO.validSend)) {
//					siVO = null;
//				}
//			}
//			
//			if(siVO.validSend) {
//				//构建XML对象
//	            String postXml = FileCopyTools.copyToString(req.getInputStream());
//				//嚓，居然可以直接调用
//				//构建头信息容器
//				Map<String,String> headerMap = new HashMap<String,String>();
//				headerMap.put("_cluster_mode_","2");
//				
//				debug("[cluster] Begin AgentCall ServerName:["+siVO.name+"] ServerGroup:["+siVO.group+"] scriptId:["+scriptId+"] ParamsContent:\n["+postXml+"]");
//				
//				//调用目标方法并获取返回值
//				String res = HttpCall.call(siVO.url()+CLUSTER_ACTION,postXml,"UTF-8","UTF-8",1,headerMap);
//				
//				debug("[cluster] Call Result ServerName:["+siVO.name+"] ServerGroup:["+siVO.group+"] scriptId:["+scriptId+"] ResultContent:\n["+res+"]");
//				
//				out(resp,res);
//			}else {
//				//通过反向触发调用
//				
//				//是否需要返回值
//				boolean noReturnValue = boo(req.getHeader("_cluster_target_no_return"));
//				//从页面请求中获取提交过来的信息
//				//解析信息数组  0来源服务器名 1来源群组名  2目标脚本主键  3传入参数  4调用类型
//				Object[] paras = getEventPara(req,resp);
//				debug("[cluster] Agent Reverse Invoke Remote Group:["+siVO.group+"] Server:["
//					      +siVO.name+"] Script:["+scriptId+"] noReturnValue:["+noReturnValue+"] paras：\n"+paras);
//				
//				//从消息容器中获取指定服务器的消息迭代器
//				RollIterator<Object[]> rit = msgMap.get(siVO.name);
//				if(rit==null) {
//					rit = new RollIterator<Object[]>(MSG_SIZE);
//					msgMap.put(siVO.name,rit);
//				}
//				
//				//构建异步处理返回值 token
//				String token = nextToken();
//				//放入队列   0token  1来源服务器名  2来源服务器所在分组名  3调用脚本名  4调用类型  5是否需要返回值  6传入参数
//				rit.add(new Object[] {token,paras[0],paras[1],paras[2],sint(paras[4]),noReturnValue,paras[3]});
//				
//				if(noReturnValue) {
//					return true; //无需返回值
//				}
//				
//				/*
//				 * 注意：以下部分为异步阻塞检查返回值
//				 */
//				int usedTime = 0; //等待返回值已经耗费的时间
//				Object value;     //返回值
//				while(usedTime<REVERSE_TRIGGER_TIME_OUT) {
//					value = fixTokenValue(token,null);
//					if(value!=null) {
//						reSbf
//							.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
//							.append("<root>")
//							.append(outParaStr(value))
//							.append("</root>");
//						out(resp,reSbf.toString());
//						return true;
//					}
//					try {
//						usedTime+=REVERSE_TRIGGER_INTERVAL_TIME;
//						Thread.sleep(REVERSE_TRIGGER_INTERVAL_TIME);
//					}catch(Exception e) {
//						break;
//					}
//				}
//				throw new TimeOutException("Wait Reverse Trigger Value Time Out");
//			}
//		}
		//已经确认是针对集群服务发来的请求，所以即使没做处理，也不用后续程序处理
		return true;
	}
	

          
	/**
	 * 判断请求
	 * @param req     页面请求
	 * @return            是否允许访问
	 * 2017年5月2日
	 * @author MBG
	 */
	@Override
    public boolean allow(IRequest req) {
		return allow(req.cip());
	}
	
	/**
	 * 判断请求
	 * @param ip     客户端IP地址
	 * @return            是否允许访问
	 * 2017年5月2日
	 * @author MBG
	 */
	@Override
    public boolean allow(String ip) {
		//分割为ip段信息数组
		String[] ips = BaseUtil.split(ip,".");
		if(ips==null || ips.length<4) {
			//地址不合法
			warning("allow IP:["+ip+"] 客户端IP地址不合法");
			return false;
		}
		for(String[] infos:allowIpList) {
			//第一段
			if("*".equals(infos[0])) {
				//麻痹的，配置信息中，第一步就是通配符，还做什么过滤
				return true;
			}
			if(!infos[0].equals(ips[0])) {
				continue;
			}
			//第二段
			if("*".equals(infos[1])) {
				return true;
			}
			if(!infos[1].equals(ips[1])) {
				continue;
			}
			//第三段
			if("*".equals(infos[2])) {
				return true;
			}
			if(!infos[2].equals(ips[2])) {
				continue;
			}
			//第四段（最后一段）
			if("*".equals(infos[3]) || infos[3].equals(ips[3])) {
				return true;
			}
		}
		return false;
	}
	
    
	/**
	 * 输出信息
	 * @param resp    页面反馈类
	 * @param content 输出内容
	 * 2016年11月19日
	 * @author MBG
	 */
	private void out(HttpServletResponse resp,String content) {
        //之前遇到个无法呈现的问题，偶尔输出内容全是问号
        //原来使用的是vh.getDealEncode();这回写死UTF-8
        //基本输出编码 weblogic只能用基本编码输出
        String standardOutEncoding = "UTF-8";
        try {
            //设置编码格式
        	resp.setCharacterEncoding(standardOutEncoding);
        }catch(NoSuchMethodError e) {}
		//设置输出编码
		resp.setHeader("Content-Type","text/xml; charset=UTF-8");
		PrintWriter writer = null; //输出流
	        try {
	        	writer = resp.getWriter();
	        	writer.write(content);
	        }catch(Exception e) {
	        }finally {
	            try {
	            	writer.flush();
	            }catch(Exception e) {}
	            try {
	            	writer.close();
	            }catch(Exception e) {}
	        }
	}
	

	/**
	 * 获取当前服务器的名字
	 * @return 当前服务器名字
	 * 2016年11月19日
	 * @author MBG
	 */
	@Override
    public String name() {
		if(isTempServerName) {
			return "PX";
		}
		return localServerVO.name;
	}
	
	/**
	 * 获取当前服务器所在的群组
	 * @return 当前服务器所在的群组
	 * 2017年4月21日
	 * @author MBG
	 */
	@Override
    public String group() {
		return localServerVO.group;
	}
	
	
	/**
	 * 集群服务器时间
	 * @return 以主服务器为参考修正了当前服务器时间后的时间（毫秒）
	 * 2016年11月19日
	 * @author MBG
	 */
	@Override
    public long time() {
		return System.currentTimeMillis()+amendTime;
	}
	
	/**
	 * 返回当前服务器时间校准差值
	 * @return 当前服务器时间校准差值
	 * 2018年7月26日
	 * @author MBG
	 */
	@Override
    public long amendTime() {
		return amendTime;
	}
	
	/**
	 * 调用主服务器动作
	 * @param scriptId   脚本主键 
	 * @param param      提交参数
	 * @param callType   调用方式  0普通调用  1调用调用主服务器  2广播调用
	 * @return           返回结果
	 * 2016年11月20日
	 * @author MBG
	 */
	@Override
    @SuppressWarnings("unchecked")
	public Object call(String scriptId,Object param,int callType) throws Exception {
		return call(masterServerName,scriptId,param,callType);
	}
	
	
	/**
	 * 调用目标服务器
	 * @param serverKey  服务器主键（名）
	 * @param scriptId   脚本主键
	 * @param param      传入参数
	 * @param callType   调用方式  0普通集群调用  1调用调用主服务器  2广播调用
	 * @return 返回信息
	 * @throws Exception 异常
	 * 2017年3月29日
	 * @author MBG
	 */
	@Override
    public Object call(String serverKey, String scriptId, Object param, int callType) throws Exception {
		//构建调用报文头
		Map<String,String> headerMap = new HashMap<String,String>();
		//获取目标（可能是路由中转服务器信息）
		ServerInfoVO siVO = getExecutableServer(serverKey,null,new ArrayList<String>(),headerMap);
		return call(siVO,headerMap,scriptId,param,callType);
	}
	
	/**
	 * 调用目标服务器
	 * @param siVO  服务器信息
	 * @param scriptId   脚本主键
	 * @param param      传入参数
	 * @param callType   调用方式  0普通集群调用  1调用调用主服务器  2广播调用
	 * @return 返回信息
	 * @throws Exception 异常
	 * 2017年3月29日
	 * @author MBG
	 */
	private Object call(
			ServerInfoVO siVO
			,Map<String,String> headerMap
			,String scriptId,Object param,int callType) throws Exception {
		if(siVO==null) {
			throw new Exception("程序出错，目标服务器信息为空");
		}
		if(siVO.local) {
			throw new Exception("程序出错，不能采用集群方式调用本机服务");
		}
		if(scriptId==null || scriptId.length()<1) {
			return null;
		}
		if(param==null) {
			param = new HashMap<String,String>();
		}
		//拼装提交值
		StringBuffer xmlSbf = new StringBuffer();
		xmlSbf
			.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
			.append("<root>")
			.append("<src_server>").append(localServerVO.name).append("</src_server>") //如果指定调用某台服务器
			.append("<src_group>").append(localServerVO.group).append("</src_group>")  //来源群组名。响应一方比对来自于不同群组，会判断这个脚本的集群属性，并且响应集群属性（调用住服务器，广播全部服务器等）
			.append("<call_type>").append(callType).append("</call_type>")             //调用方式  0普通集群调用  1调用调用主服务器  2广播调用
			.append("<script>").append(scriptId).append("</script>")
			.append("<sn>").append(getSn()).append("</sn>")                            //序列号调用返回结果中也会包含这个值，在查日志时就能找到对应的调用信息以及对应的返回信息了
			.append(getSerializer().serialize(param))
			.append("</root>");
		
		//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
		//if(siVO.invalidServer) {
		//	throw new MsgException(this,"The Server Is Invalid[Call Cluster Return 404] serverKey:["+serverKey+"]");
		//}
		//集群调用模式  2普通直接调用  7代理调用
		int clusterMode = sint(headerMap.get("_cluster_mode_"));
		if(clusterMode==0) {
			debug("[cluster] Begin Call Server:["+siVO.name+"] Group:["+siVO.group+"] scriptId:["+scriptId+"] ParamsContent:\n["+xmlSbf+"]");
			headerMap.put("_cluster_mode_","2");
			headerMap.put("_cluster_call_",localServerVO.name);
		}else if(clusterMode==7){
			//代理调用
			debug("[cluster] Begin Route Call scriptId:["+scriptId+"] Source:["
			+headerMap.get("_cluster_route_src_")+"] FinalTarget:["
			+headerMap.get("_cluster_route_obj_")+"] RouteLevel:["
			+siVO.routeLevel+"] ThisServer:["+siVO.name+"]");
		}
		//else 如果是通过代理方式headerMap已经放入了代理所需的参数信息
		
		//调用目标方法并获取返回值
		IViewHandler res = HttpCall.xCall(siVO.url()+CLUSTER_ACTION,xmlSbf.toString(),1,headerMap);
		
		debug("[cluster] Call Result Server:["+siVO.name+"] Group:["+siVO.group+"] scriptId:["+scriptId+"] ResultContent:\n["+res+"]");
		
		//返回参数绝对不可能为 ActionContext
		Object reObj = getSerializer().unserialize(res.fnn("v"),null,null,true);
		if(reObj==null) {
			return null;
		}
		return reObj;
	
	}
	
	
	/**
	 * 调用本机指定脚本
	 * @param scriptId 脚本主键
	 * @param param    传入参数
	 * @return         返回值
	 * 2016年12月14日
	 * @author MBG
	 */
	@SuppressWarnings("unchecked")
	private Object callSelf(String scriptId,Object param){
		try {
			if(scriptId==null || scriptId.length()<1) {
				return null;
			}
			/*
			 * 注意：在这个过滤器中的callType的值是这样定义的： 
			 *      0普通集群调用  1调用集群主服务器  2广播调用
			 *      
			 *      而在ScriptLoader中，也存在callType，是这样定义的：
			 *      0本地调用   1调用主服务器  2广播调用  99由集群发起，响应外部服务器调用
			 *      
			 */
			if(param==null) {
				//原本这里传1，被改为99，因为callSelf不就是调用本服务器的方法嘛，那就是99
				return sl.invokeScript(this,scriptId,99);
			}else if(param instanceof IActionContext) {
				return sl.invokeScript(this,scriptId,(IActionContext)param,99);
			}
			if(!(param instanceof Map)) {
				warning("--------The Script:["+scriptId+"] In Parameter:["
							+param+"] Is Not Support. Only Support Map<String,?>");
				warning(DebugUtil.getStackTraceString(this));
				param = null;
			}
			return sl.invokeScript(this,scriptId,(Map<String,?>)param,99);
		}catch(Throwable e) {
			e.printStackTrace();
			return e;
		}
	}

	/**
	 * 获取接收到的参数
	 * @param req         页面请求
	 * @param resp        页面反馈
	 * @return               解析信息数组  0来源服务器名 1来源群组名  2目标脚本主键  3传入参数  4调用类型  5调用序列号
	 * @throws Exception  异常
	 * 2017年4月22日
	 * @author MBG
	 */
	private Object[] getEventPara(IRequest req,IResponse resp) throws Exception {
		//构造返回值
		Object[] res = new Object[6];
		//编码格式
		String enc = req.getHeader("encoding");
		if(enc==null || enc.length()<1) {
			enc = req.getCharacterEncoding();
			if(enc==null || enc.length()<1) {
				enc = "UTF-8";
			}
		}
		//构建XML对象
		INodeHandler infoXml = FNodeHandler.newNodeHandler();
		infoXml.setXmlStyle(true);
		//从头信息中获取，或者从url参数中获取是否做Base64解码
		if(boo(req.getHeader("base64_enc")) 
				|| boo(req.getUrlParameter("base64_enc"))) {
			String content = FileCopyTools.copyToString(req.getInputStream());
			if(content.startsWith("@b64@")) {
				content = content.substring(5);
			}
			//读取完整的内容并做Base64解密
			infoXml.setNodeBody(Base64.base64Decode(content,enc));
		}else {
			//直接将读入流设置到xml对象中
			infoXml.setStream(req.getInputStream(),"ServletExplorer");
		}
		//调用这个方法的地方，随后都输出了日志，这里再输出就多余了
		//debug("[cluster] getEventParaMsg:\n"+infoXml.getNodeBody());
		//来源群组名
		String srcGroup  = infoXml.fnn("src_group").nt();
		//来源服务器名
		String srcServer = infoXml.fnn("src_server").nt();
		
		if(srcGroup.length()>0 || srcServer.length()>0) {
			ServerInfoVO siVO = null; //服务器信息类
			if(srcServer.length()<1) {
				if(srcGroup.length()>0) {
					//没有服务器名，但是给了组名。取该组的主服务器
					
					//获取该群组全部服务器
					SListMap<ServerInfoVO> sListMap = groupServerMap.get(srcGroup);
					for(ServerInfoVO ele:sListMap) {
						if(ele.master) {
							siVO = ele;
							break;
						}
					}
					if(siVO!=null) {
						srcServer = siVO.name;
					}
				}
			}else if(srcGroup.length()<1) {
				//没有组名，但是写了服务器名
				siVO = serverMap.get(srcServer);
				if(siVO!=null) {
					srcGroup= siVO.group;
				}
			}
		}
		//来源服务器名
		res[0] = srcServer;
		//来源组名
		res[1] = srcGroup;
		//目标脚本主键
		res[2] = infoXml.fnn("script").nt();
		//解析出传入参数对象
		res[3] = getSerializer().unserialize(infoXml.fnn("v"),req,resp,true);
		//调用类型   0普通调用（点对点）  1只调用主服务器  2广播调用
		res[4] = String.valueOf(sint(infoXml.fnn("call_type").nt()));
		//调用序列号，用来在日志中匹配请求参数和返回参数
		res[5] = infoXml.fnn("sn").nt();
		return res;
	}
	
	/**
	 * 处理内部触发响应
	 * @param req         页面请求
	 * @param resp        页面反馈
	 * @throws Exception  异常
	 * 2019年10月8日
	 * @author MBG
	 */
	private void executeTrigger(String triggerKey,ServerInfoVO siVO,IRequest req,IResponse resp) throws Exception {
		//获取触发类
		IClusterTrigger trigger = triggerBeans.get(triggerKey);
		if(trigger==null) {
			resp.sendError(404,"Not Find The TriggerKey:["+triggerKey+"] RemoteServer:["+req.getRemoteAddr()+"]");
			return;
		}
		//响应请求
		trigger.triggerRecive(siVO,req,resp);
	}
	
	/**
	 * 调用内部程序
	 * @param req        页面请求
	 * @param resp       页面反馈
	 * @throws Exception 异常
	 * 2017年3月29日
	 * @author MBG
	 */
	private void executeEvent(IRequest req,IResponse resp) throws Exception {
		if(localServerVO==null) {
			//在还没获取到本机服务器名时，响应其它服务器请求时，会报空指针
			return;
		}
		//判断是否为上传模式（需要在开头判断，不能读取报文体内容）
		String uploadScript = req.getHeader("_cluster_upload_script_");
		if(uploadScript!=null && uploadScript.length()>0) {
			//上传模式
			if(sint(req.getHeader("_cluster_mode_"))==7) {
				debug("[cluster] Remote Route Upload From["
					+req.getRemoteAddr()+"] Source:["+req.getHeader("_cluster_route_src_")
					+"] RouteLevel:["+req.getHeader("_cluster_route_level_")+"]");
			}else {
				debug("[cluster] Remote Upload From["+req.getRemoteAddr()+"]");
			}
			//执行目标动作
			rcf.invokeScriptAction(uploadScript,req,resp);
			return;
		}
		
		//解析返回信息  0服务器名 1来源群组名  2目标脚本主键  3传入参数  4调用类型  5调用序列号
		Object[] resInfos = getEventPara(req,resp);
		
		debug("[cluster] Remote From Group:["+resInfos[1]+"] Server:["+resInfos[0]+"] LocalGroup:["
				+localServerVO.group+"] LocalServer:["+localServerVO.name+"] ExecuteEvent  ScriptId:["
				+resInfos[2]+"] CallType:["+resInfos[4]+"] sn:["+resInfos[5]+"] Params:["+DebugUtil.objectInfo(resInfos[3])+"]");
		
		//---------------累加调用计数器---------------
		if(invokeCount==Integer.MAX_VALUE) {
			invokeCount = 0;
			if(invokeClearFlag==Integer.MAX_VALUE) {
				invokeClearFlag = 0;
			}else {
				invokeClearFlag++;
			}
		}else {
			invokeCount++;
		}
		//---------------累加调用计数器---------------
		
		StringBuffer reSbf = new StringBuffer(); //返回信息字符串
		reSbf
			.append("<?xml version=\"1.0\" encoding='UTF-8'?><root group=\"")
			.append(localServerVO.group)
			.append("\" name=\"")
			.append(localServerVO.name)
			.append("\" sn=\"").append(resInfos[5]).append("\">");
		
		if(!resInfos[1].equals(localServerVO.group)) {
			//来自其它群组调用过来的，需要判断本地脚本的集群属性，并执行
			if(!sl.hasScript((String)resInfos[2])) {
				reSbf
					.append(
							getSerializer().serialize(
									new Exception(
											"Not Find The Script:["
											+resInfos[2]+"] TargetServer:["
											+localServerVO.name+"]")));
			}else {
				if("1".equals(resInfos[4])) {
					///////////////////////////////////调用主服务器///////////////////////////////////
					if(localServerVO.master) {
						//当前服务器就是主服务器
						//调用目标
						Object res = callSelf((String)resInfos[2],resInfos[3]);
						reSbf.append(getSerializer().serialize(res));
					}else {
						//当前服务器并非主服务器
						
						//获取当前群组的主服务器信息
						ServerInfoVO siVO = getMasterServer(localServerVO.group);
						if(siVO==null) {
							reSbf
								.append(
										getSerializer().serialize(
												new Exception(
														"Not Find The TargetGroup Master Server.The Script:["
														+resInfos[2]+"] EventServer:["
														+localServerVO.name+"]")));
						}else {
							try {
								//调用目标服务器   在这里传入0  这里已经认定它是主机了，就别转来转去的了
								Object res = call(siVO.name,(String)resInfos[2],resInfos[3],0);
								reSbf.append("<status>1</status>");
								if(res!=null) {
									reSbf.append(getSerializer().serialize(res));
								}
							}catch(Exception e) {
								e.printStackTrace();
								reSbf.append(getSerializer().serialize(e));
							}
						}
					}
				}else if("2".equals(resInfos[4])){
					///////////////////////////////////广播调用///////////////////////////////////
					try {
						//先调用当前服务器
						callSelf((String)resInfos[2],resInfos[3]);
						//广播调用其它服务器（不包含当前服务器）
						callAll((String)resInfos[2],resInfos[3]);
						reSbf.append("<status>1</status>").append(getSerializer().serialize(null));
					}catch(Exception e) {
						e.printStackTrace();
						reSbf.append(getSerializer().serialize(e));
					}
				}else {
					//就是调用本机
					
					//调用目标
					Object res = callSelf((String)resInfos[2],resInfos[3]);
					reSbf.append(getSerializer().serialize(res));
				}
			}
		}else {
			try {
				//调用目标
				Object res = callSelf((String)resInfos[2],resInfos[3]);
				reSbf.append(getSerializer().serialize(res));
			}catch(Exception e) {
				e.printStackTrace();
				reSbf.append(getSerializer().serialize(e));
			}
		}
		reSbf.append("</root>");
		
		debug("[cluster] Remote From Group:["+resInfos[1]+"] Server:["+resInfos[0]+"] LocalGroup:["
				+localServerVO.group+"] LocalServer:["+localServerVO.name+"] ExecuteEvent  ScriptId:["
				+resInfos[2]+"] CallType:["+resInfos[4]+"] sn:["+resInfos[5]+"] res:["+reSbf+"]");
		try {
			//设置编码格式
			resp.setCharacterEncoding("UTF-8");
			resp.getWriter().print(reSbf.toString());
		}catch(NoSuchMethodError e) {
			//使用WebLogic时会抛异常到这里，然后使用标准输出
			//非weblogic不能使用标准输出，否则反而出现乱码
			resp.getWriter().write(new String(reSbf.toString().getBytes(), StandardCharsets.ISO_8859_1));
		}
	}
	
	
	/**
	 * 以文件上传方式
	 * @param keyName         目标服务器主键或目标群组主键
	 * @param scriptId        调用目标脚本
	 * @param params          提交参数字符串 key1=val1&key2=val2
	 * @param uploadNameList  上传文件对象名（目标脚本参数名）序列
	 * @param fileNameList    源文件名序列
	 * @param filePathList    需要上传的文件绝对路径序列
	 * @param invoker         调用者
	 * @return                返回字符串信息
	 * @throws Exception      异常
	 * 2019年12月10日
	 * @author MBG
	 */
	public String routeUpload(
			 String keyName
			,String scriptId
			,String params
			,List<String> uploadNameList
			,List<String> fileNameList
			,List<String> filePathList
			,Object invoker) throws Exception {
		//调用所需的报文头对象
		Map<String,String> headerMap = new HashMap<String,String>();
		//获取指定群组中的主服务器信息
		ServerInfoVO siVO = getExecutableServer(keyName,null,new ArrayList<String>(),headerMap);
		if(siVO==null) {
			throw new Exception("The ScriptID:["+scriptId+"] ServerName:["+keyName+"] invoker:["+invoker+"] Not Find The Server");
		}
		if(!siVO.validSend) {
			//无法上传到目标服务器（上传功能不支持反向触发，实际上可以触发，在以后有实际需求时再开发）
			throw new Exception("The ScriptID:["+scriptId+"] KeyName:["+keyName+"] TargetGroupName:["
					+siVO.group+"] TargetServerName:["+siVO.name+"] ServerURL:["+siVO.url+"] invoker:["+invoker+"] Can't Upload To");
		}
		//标记为上传模式
		headerMap.put("_cluster_upload_script_",scriptId);
		int clusterMode = sint(headerMap.get("_cluster_mode_"));
		if(clusterMode==0) {
			debug("[cluster] Begin Upload Server:["+siVO.name+"] Group:["+siVO.group+"] scriptId:["
					+scriptId+"] ");
			headerMap.put("_cluster_mode_","2");
			headerMap.put("_cluster_call_",localServerVO.name);
		}else if(clusterMode==7) {
			//代理调用
			debug("[cluster] Begin Route Call scriptId:["+scriptId+"] Source:["
			+headerMap.get("_cluster_route_src_")+"] FinalTarget:["
			+headerMap.get("_cluster_route_obj_")+"] RouteLevel:["
			+siVO.routeLevel+"] ThisServer:["+siVO.name+"]");
		}else {
			debug("[cluster] Upload Remote Group:["+siVO.group+"] Server:["
					+siVO.name+"] RouteLevel:["+siVO.routeLevel
					+"] Script:["+scriptId+"] invoker:["+invoker+"]");
		}
		//直接调用目标服务器执行上传
		return HttpCall.upload(
				 	 siVO.url()+CLUSTER_ACTION
					,params
					,uploadNameList
					,fileNameList
					,filePathList
					,"UTF-8"
					,"UTF-8"
					,headerMap);
	}
	
	
	/**
	 * 调用其他服务器上的脚本程序
	 * 
	 * 调用目标脚本业务逻辑：
	 * 
	 * 1.直接调用
	 * 2.通过代理调用
	 * 3.反向触发调用
	 * 4.没法调用
	 * 
	 * @param keyName           目标群组名或者指定服务器名
	 * @param scriptId          目标脚本名
	 * @param param             传入参数
	 * @param callType          调用方式   0普通调用（也为主服务器或指定服务器调用） 1主服务器调用   2广播调用
	 * @param invoker           调用者
	 * @param noReturnValue     是否不需要返回值 
	 *                        （因为是目标服务器，本机是无法通过脚本信息类判定目标脚本带不带返回值，只能手工强制无需返回值） 
	 *                          这中情况主要用于反向应触发调用服务，只要通知前置机，无需获取返回值。这样就无需轮询等待返回值，使远程调用
	 * @return                  调用返回值
	 * @throws Exception        异常
	 * 2017年4月22日
	 * @author MBG
	 */
	@Override
    public Object routeCall(
			String keyName
			,String scriptId
			,Object param
			,int callType
			,Object invoker
			,boolean noReturnValue) throws Exception {
		//转换为其它方法中的调用类型值   0普通调用  1主服务器调用 2广播调用
		if(callType!=2) {
			callType = 1;  //除了广播调用，都是主服务器或指定服务器调用
		}
		//调用所需的报文头对象
		Map<String,String> headerMap = new HashMap<String,String>();
		//获取指定群组中的主服务器信息
		ServerInfoVO siVO = getExecutableServer(keyName,null,new ArrayList<String>(),headerMap);
		if(siVO==null) {
			throw new Exception("The ScriptID:["+scriptId+"] ServerName:["+keyName+"] invoker:["+invoker+"] Not Find The Server");
		}
		if(siVO.validSend) {
			debug("[cluster] Invoke Remote Group:["+siVO.group+"] Server:["+siVO.name+"] RouteLevel:["+siVO.routeLevel+"] Script:["+scriptId+"] invoker:["+invoker+"]");
			//直接调用目标服务器
			return call(siVO,headerMap,scriptId,param,callType);
		}
		if(siVO.validReceive) {
			//反向触发调用服务
			/*
			 * 如果外部服务器通过Nginx等软件访问内部负载均衡的服务器组
			 * 是否能够接收到外部服务器消息，这个变量值，会定期检查接收心跳包超时（多久没收到心跳包）线程检测并修改该值
			 * 也就是说，如果长时间没响应
			 */
			/*
			 * 反向触发调用：
			 * 
			 * 用在服务器A（外网服务器）可以访问服务器B（内网服务器）
			 * ，但是服务器B无法访问服务器A的单向情况。
			 * 
			 * 如果服务器B想调用服务器A,利用集群中的心跳处理功能，服务器B
			 * 将调用服务器A的信息放在心跳反馈容器中，服务器A执行访问服务器B
			 * 心跳时，主动获取到服务器B想要调用自己的信息，然后由服务器A主动
			 * 调用服务器B，最终实现了服务器B调用服务器A 
			 */
			debug("[cluster] Reverse Invoke Remote Group:["+siVO.group+"] Server:["
				      +siVO.name+"] Script:["+scriptId+"] invoker:["+invoker+"] noReturnValue:["+noReturnValue+"]");
			
			//从消息容器中获取指定服务器的消息迭代器
			RollIterator<Object[]> rit = msgMap.get(siVO.name);
			if(rit==null) {
				rit = new RollIterator<Object[]>(MSG_SIZE);
				msgMap.put(siVO.name,rit);
			}
			
			//构建异步处理返回值 token
			String token = nextToken();
			//放入队列

			//0token  1来源服务器名  2来源服务器所在分组名  3调用脚本名  4调用类型  5是否需要返回值  6传入参数
			rit.add(new Object[] {token,localServerVO.name,localServerVO.group,scriptId,callType,noReturnValue,param});
			
			if(noReturnValue) {
				//无需返回值
				return null;
			}
			
			//放入token当前时间
			tokenMap.put(token,System.currentTimeMillis());
			
			/*
			 * 注意：以下部分为异步阻塞检查返回值
			 */
			int usedTime = 0; //等待返回值已经耗费的时间
			//如果多次调用反向触发，第一次调用时发生超时，其它调用也无需等待，直接从msgMap中移除队列信息
			while(usedTime<REVERSE_TRIGGER_TIME_OUT && msgMap.containsKey(siVO.name)) {
				//获取值 数组0 是否存在值  1返回值
				Object[] res = fixTokenValue(token,null,false);
				if((Boolean)res[0]) {
					//存在值
					tokenMap.remove(token);
					return res[1];
				}
				try {
					usedTime+=REVERSE_TRIGGER_INTERVAL_TIME;
					Thread.sleep(REVERSE_TRIGGER_INTERVAL_TIME);
				}catch(Exception e) {
					break;
				}
			}
			msgMap.remove(siVO.name); //放弃获取反向触发数据
			throw new TimeOutException("Wait Reverse Trigger Value Time Out ScriptID:["+scriptId+"] KeyName:["+keyName+"] GroupName:["+ 
					       siVO.group+"] ServerName:["+siVO.name+"] ServerURL:["+siVO.url+"] invoker:["+invoker+"]");
		}
		
		//if(!localServerVO.agentBalance) {
			//不光无法发送消息，也无法接收消息
			throw new Exception("The ScriptID:["+scriptId+"] KeyName:["+keyName+"] GroupName:["
			  +siVO.group+"] ServerName:["+siVO.name+"] ServerURL:["+siVO.url+"] invoker:["+invoker+"] Not Connected");
		//}
		
//以下代码已废弃：不用委托集群中其它服务器做反向触发调用，具体原因请看顶部类文件说明（废弃mode=4,5）
//
//		//反向触发调用服务
//		/*
//		 * 新情况：
//		 * 
//		 *         实际运行时，遇到这种情况，前台调用后台Nginx，由Nginx负
//		 * 债均衡随机跳转到后台集群中的一个服务器中（只能随机调用后台其中一台
//		 * 服务器），后台无法调用前台方法。
//		 *         反向触发调用时，后台服务器A发起反向触发调用。但是前台服务器
//		 * 一直通过Nginx与后台服务器B做沟通，无法联系到后台服务器A，那么无法从
//		 * 后台服务器A上获取到反向触发的数据，导致反向触发调用失败。
//		 */
//		
//		//构建提交头对象
//		Map<String,String> headerMap = new HashMap<String,String>();
//		headerMap.put("_cluster_mode_","4");
//		headerMap.put("_cluster_target_name",keyName);
//			
//		List<ServerInfoVO> sList = getServerInfoList(localServerVO.group);
//		ServerInfoVO tarSiVO = null; //合适的服务器
//		IViewHandler res; //返回信息对象
//		for(ServerInfoVO ele:sList) {
//			try {
//				res = HttpCall.xCall(ele.url+ele.context+CLUSTER_ACTION,null,1,headerMap);
//			}catch(Exception e) {
//				continue;
//			}
//			if(!res.fnn("status").nt().equals("1")) {
//				continue;
//			}
//			tarSiVO = ele;
//			break;
//		}
//		
//		if(tarSiVO==null) {
//			throw new Exception("The ScriptID:["+scriptId+"] KeyName:["+keyName+"] GroupName:["
//					  +siVO.group+"] ServerName:["+siVO.name+"] invoker:["+invoker+"] Can't Execute Trigger Call");
//		}
//		//拼装报文头
//		headerMap = new HashMap<String,String>();
//		headerMap.put("_cluster_mode_","5");
//		headerMap.put("_cluster_target_name",keyName);
//		headerMap.put("_cluster_target_script",scriptId);
//		headerMap.put("_cluster_target_no_return",noReturnValue?"1":"0");
//		headerMap.put("_cluster_target_call_type",str(callType));
//		
//		//拼装提交值
//		StringBuffer xmlSbf = new StringBuffer();
//		xmlSbf
//			.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
//			.append("<root>")
//			.append("<src_server>").append(localServerVO.name).append("</src_server>") //如果指定调用某台服务器
//			.append("<src_group>").append(localServerVO.group).append("</src_group>") //来源群组名。响应一方比对来自于不同群组，会判断这个脚本的集群属性，并且响应集群属性（调用住服务器，广播全部服务器等）
//			.append("<call_type>").append(callType).append("</call_type>") //调用方式  0普通调用  1调用调用主服务器  2广播调用
//			.append("<script>").append(scriptId).append("</script>");
//		if(param!=null) {
//			xmlSbf.append(outParaStr(param)); 
//		}
//		xmlSbf.append("</root>");
//		
//		//调用相同分组集群中，可以调用到目标的服务器来调用，并且获取返回值
//		String reContent = HttpCall.call(tarSiVO.url()+CLUSTER_ACTION,xmlSbf.toString(),"UTF-8","UTF-8",1,headerMap);
//		if(noReturnValue || reContent==null || reContent.length()<1) {
//			return null;
//		}
//		//构建XML解析对象
//		IViewHandler reVh = FNodeHandler.newBody(reContent);
//		//解析返回值
//		return outParaObj(reVh.fnn("v"),null,null);
	}
	
	/**
	 * 获取可以调通的服务器信息VO
	 * @param serverName       目标服务器名
	 * @param headerMap        报文头信息容器
	 * @return                 直接返回目标服务器信息VO，或者代理的服务器信息VO
	 * 2019年9月26日
	 * @author MBG
	 */
	public ServerInfoVO getExecutableServer(
			 String serverName
			,Map<String,String> headerMap) {
		return getExecutableServer(serverName,null,new ArrayList<String>(),headerMap);
	}
	
	
	/**
	 * 获取可以调通的服务器信息VO
	 * @param serverName       目标服务器名
	 * @param callServerName   发起服务器名（首发为空）
	 * @param routeGroupList   已经经过的路由分组名序列（在接下来路由时，不再向这些分组发送路由请求，避免路由风暴） 
	 * @param headerMap        报文头信息容器
	 * @return                 直接返回目标服务器信息VO，或者代理的服务器信息VO
	 * 2019年9月26日
	 * @author MBG
	 */
	private ServerInfoVO getExecutableServer(
			 String            serverName
			,String            callServerName
			,List<String>       routeGroupList
			,Map<String,String> headerMap) {
		ServerInfoVO siVO = null; //目标服务信息类
		if(groupServerMap.containsKey(serverName)) {
			if(invokeLoadBalancing) {
				//负载均衡方式
				siVO = getBalanceServer(serverName);
				if(siVO==null) {
					return null;
				}
			}else {
				//只调用主服务器模式
				siVO = getMasterServer(serverName);
				if(siVO==null) {
					//群组服务器信息序列
					SListMap<ServerInfoVO> gsMap = groupServerMap.get(serverName);
					if(gsMap!=null && gsMap.size()>0) {
						siVO = gsMap.get(0);
					}
					if(siVO==null) {
						return null;
					}
				}
			}
		}else {
			//尝试直接获取可以调用通的目标服务器信息容器
			siVO = serverMap.get(serverName);
			if(siVO==null) {
				//目标服务器配置信息不存在
				return null;
			}
		}
		if(siVO.validSend) {
			siVO.routeLevel = 0;
			return siVO;
		}
		if(siVO.unableRoute) {
			//已经试过，无法通过路由中转方式调用
			return siVO;
		}
		ServerInfoVO reVO = null; //构建返回值
		if(siVO.routeName!=null && siVO.routeName.length()>0) {
			reVO = getServerInfo(siVO.routeName);
		}
		if(!routeGroupList.contains(localServerVO.group)) {
			routeGroupList.add(localServerVO.group);
		}
		if(reVO==null || !reVO.validSend) {
			ServerInfoVO ele;             //服务器信息容器元素
			int          level = -1;      //中转层级次数
			int          tLevel;          //本次中转层级次数
			
			//构建查询请求报文头对象
			Map<String,String> hMap = new HashMap<String,String>();
			hMap.put("_cluster_mode_","6"); //设置为查询目标是否可以请求
			hMap.put("_cluster_route_obj_",serverName);
			hMap.put("_cluster_route_caller_",name());
			hMap.put("_cluster_route_groups_",StringUtil.list2str(routeGroupList,","));
			
			
			for(int i=0;i<serverMap.size();i++) {
				ele = serverMap.get(i);
				if(ele==null 
						|| ele.local 
						|| localServerVO.group.equals(ele.group)
						|| routeGroupList.contains(ele.group)
						|| !ele.validSend 
						|| (callServerName!=null 
						&& callServerName.equals(ele.name))) {
					continue;
				}
				hMap.put("_cluster_route_status_","0");
				hMap.put("_cluster_route_level_","1");
				try {
					//发起请求
					post(ele,CLUSTER_ACTION,null,0,hMap);

					if(boo(hMap.get("_cluster_route_status_"))) {
						//发现中转服务器
						tLevel = sint(hMap.get("_cluster_route_level_"));
						if(level<0 || level>tLevel) {
							level = tLevel;
							reVO  = ele;
						}
					}
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
			if(reVO!=null) {
				siVO.routeName  = reVO.name;
				siVO.routeLevel = level+1;
			}else {
				//无法访问目标服务器，也无法通过代理方式访问目标服务器
				siVO.unableRoute = true;
				return siVO;
			}
		}
		/*
		 * 程序走到这里，siVO直接调用肯定没戏，但reVO是可以路由到目标服务器的，先提前在这里拼装好
		 * 需要路由到目标的信息
		 */
		reVO = getServerInfo(siVO.routeName);
		//如果没有获取到目标路由服务器信息，或者目标路由服务器信息已经在途径路由群组信息中，则不能继续路由到这台服务器中
		if(reVO==null || routeGroupList.contains(reVO.group)) {
			if(reVO==null) {
				warning("The Server:["+siVO.name+"] In Group:["+siVO.group+"] Not Find RouteServer:["+siVO.routeName+"]");
			}else {
				//标记目标路由地址是死循环
				warning("The Server:["+siVO.name+"] In Group:["+siVO.group+"] RouteServer:["+siVO.routeName+"] In Ring:["+StringUtil.list2str(routeGroupList,","));
			}
			siVO.routeName  = null;
			siVO.routeLevel = -1;
			return siVO;
		}
		if(headerMap!=null) {
			headerMap.put("_cluster_mode_","7");                                      //通过代理中转
			headerMap.put("_cluster_route_src_",name());                              //发起方
			headerMap.put("_cluster_route_obj_",serverName);                          //目标服务器
			headerMap.put("_cluster_route_level_",String.valueOf(siVO.routeLevel));  //路由层级
			headerMap.put("_cluster_route_caller_",name());                           //上一级调用方
			headerMap.put("_cluster_route_groups_",StringUtil.list2str(routeGroupList,",")); //已经路由过的分组名（在下一级路由请求时，不再路由这些分组服务器）
		}
		return reVO;
	}
	
	
	/**
	 * 设置或获取返回值
	 * @param token 涛啃
	 * @param value 异步设置的返回值
	 * @param isSet 是否设置值
	 * @return 如果是获取值   数组0：是否存在值   数组1：值（可能为null)
	 * 2017年4月22日
	 * @author MBG
	 */
	private synchronized Object[] fixTokenValue(String token,Object value,boolean isSet) {
		if(isSet) {
			reMsgMap.put(token,value);
			return null;
		}
		//构建返回值
		Object[] res = new Object[2];
		if(reMsgMap.containsKey(token)) {
			res[0] = Boolean.TRUE;
			res[1] =  reMsgMap.remove(token);
		}else {
			res[0] = Boolean.FALSE;
			res[1] = null;
		}
		return res;
	}
	
	/**
	 * 获取新的token值
	 * @return 新的token值
	 * 2017年4月22日
	 * @author MBG
	 */
	private synchronized String nextToken() {
		if(tokenIndex>999999999) {
			tokenIndex = 0;
		}
		return SString.valueOf(tokenIndex++);
	}
	
	
	
	/**
	 * 调用主服务器动作
	 * @param scriptId 脚本主键
	 * @param callType   调用方式  0普通调用  1调用调用主服务器  2广播调用
	 * @return       返回结果
	 * @throws       异常
	 * 2016年11月20日
	 * @author MBG
	 */
	@Override
    @SuppressWarnings("unchecked")
	public Object call(String scriptId,int callType) throws Exception {
		return call(scriptId,null,callType);
	}
	
	
	/**
	 * 向集群中指定服务器发起请求
	 * @param serverName  集群中配置的服务器名（主键）
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象    XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型   0key=value&key=value  1XML 2JSON
	 * @param headerKeys  提交报文头主键
	 * @param headerVals  提交报文数据
	 * @param respHeader  报文头数据
	 * @return            返回数据流对象
	 * @throws Exception  异常
	 * 2019年9月4日
	 * @author MBG
	 */
	public InputStream postRis(
			 String             serverName
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		//获取目标服务器信息容器
		ServerInfoVO siVO = getServerInfo(serverName);
		if(siVO==null) {
			throw new Exception("Not Find The Server:["+serverName+"] In ClusterGroup");
		}
		return postRis(siVO,subUrl,postData,contentType,headerKeys,headerVals,respHeader);
	}
	
	
	/**
	 * 向集群中指定服务器发起请求
	 * @param serverName  集群中配置的服务器名（主键）
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象   XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型   0key=value&key=value  1XML 2JSON
	 * @param header      报文头数据
	 * @return            返回数据流对象
	 * @throws Exception  异常
	 * 2019年9月4日
	 * @author MBG
	 */
	public InputStream postRis(
			 String             serverName
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		//获取目标服务器信息容器
		ServerInfoVO siVO = getServerInfo(serverName);
		if(siVO==null) {
			throw new Exception("Not Find The Server:["+serverName+"] In ClusterGroup");
		}
		return postRis(siVO,subUrl,postData,contentType,header);
	}
	
	/**
	 * 向集群中指定服务器发起请求
	 * @param siVO         集群中服务器信息类
	 * @param subUrl       调用目标的相对路径
	 * @param postData     提交数据对象  XML和JSON 也可以是字符串类型
	 * @param contentType  提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param headerKeys   报文头主键数组
	 * @param headerVals   报文头值数组
	 * @param respHeader   返回报文头数据
	 * @return             返回数据流对象
	 * @throws Exception   异常
	 * 2019年9月4日
	 * @author MBG
	 */
	public InputStream postRis(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		return HttpCall.call(siVO.url()+subUrl,postData,contentType,headerKeys,headerVals,respHeader,"UTF-8");
	}
	
	
	/**
	 * 向集群中指定服务器发起请求
	 * @param siVO         集群中服务器信息类
	 * @param subUrl       调用目标的相对路径
	 * @param postData     提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType  提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param header       返回报文头数据
	 * @return             返回数据流对象
	 * @throws Exception   异常
	 * 2019年9月4日
	 * @author MBG
	 */
	public InputStream postRis(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		return HttpCall.call(siVO.url()+subUrl,postData,contentType,header,"UTF-8");
	}
	
	/**
	 * 向集群中指定服务器发起请求（无返回信息）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param headerKeys  报文头主键数组
	 * @param headerVals  报文头值数组
	 * @param respHeader  返回报文头数据
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public void post(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		if(siVO==null) {
			return;
		}
		InputStream is = null;
		try {
			is = HttpCall.call(siVO.url()+subUrl,postData,contentType,headerKeys,headerVals,respHeader,"UTF-8");
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
		}
	}
	
	
	/**
	 * 向集群中指定服务器发起请求（无返回信息）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param header      返回报文头数据
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public void post(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		InputStream is = null;
		try {
			is = HttpCall.call(siVO.url()+subUrl,postData,contentType,header,"UTF-8");
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
		}
	}
	
	/**
	 * 想集群中指定服务器做代理调用
	 * @param  siVO            指定集群中目标服务器
	 * @param  subUrl          调用目标的相对路径
	 * @param  header          自定义报文头数据
	 * @param  req             请求对象
	 * @param  resp            反馈对象
	 * @param redirBaseurl     如果重定向，需要设置的根路径
	 * @param  sessionKeyName  在Cookie中，存放会话主键值的名
	 * @param  sessionKeyValue 上次调用该服务器时返回的会话信息值
	 * @param  本次调用后返回  0状态码   1状态信息    2目标系统会话主键
	 * @throws Exception  异常
	 * 2019年9月12日
	 * @author MBG
	 */
	public String[] post(
			 ServerInfoVO       siVO
			,String             subUrl
			,Map<String,String> header
			,IRequest           req
			,IResponse          resp
			,String             redirBaseurl
			,String             sessionKeyName
			,String             sessionKeyValue) throws Exception {
		return HttpCall.call(siVO.url()+subUrl,header,req,resp,null,redirBaseurl,sessionKeyName,sessionKeyValue);
	}
	
	/**
	 * 向集群中指定服务器发起请求（返回字符串信息）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param headerKeys  提交报文头主键
	 * @param headerVals  提交报文数据
	 * @param respHeader  报文头数据
	 * @return            返回字符串值
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public String postRstr(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		//构建返回值
		ByteOutputStream bos    = new ByteOutputStream();
		InputStream      is     = null; //返回信息读入流
		try {
			is = HttpCall.call(siVO.url()+subUrl,postData,contentType,headerKeys,headerVals,respHeader,"UTF-8");
			bos.write(is);
			return bos.toString();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			try {
				bos.close();
			}catch(Exception e2) {}
		}
	}
	
	
	/**
	 * 向集群中指定服务器发起请求（返回字符串信息）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param header      报文头数据
	 * @return            返回字符串值
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public String postRstr(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		//构建返回值
		ByteOutputStream bos    = new ByteOutputStream();
		InputStream      is     = null; //返回信息读入流
		try {
			is = HttpCall.call(siVO.url()+subUrl,postData,contentType,header,"UTF-8");
			bos.write(is);
			return bos.toString();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			try {
				bos.close();
			}catch(Exception e2) {}
		}
	}
	
	/**
	 * 向集群中指定服务器发起请求（返回Json信息对象）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param headerKeys  提交报文头主键
	 * @param headerVals  提交报文数据
	 * @param respHeader  报文头数据
	 * @return            返回Json信息对象
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public Json postRjson(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		return new Json(postRstr(siVO,subUrl,postData,contentType,headerKeys,headerVals,respHeader));
	}
	
	
	/**
	 * 向集群中指定服务器发起请求（返回Json信息对象）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param header      报文头数据
	 * @return            返回Json信息对象
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public Json postRjson(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		return new Json(postRstr(siVO,subUrl,postData,contentType,header));
	}
	
	/**
	 * 向集群中指定服务器发起请求（返回XML对象）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param headerKeys  提交报文头主键
	 * @param headerVals  提交报文数据
	 * @param respHeader  报文头数据
	 * @return            返回XML对象
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public IViewHandler postRvh(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,String[]           headerKeys
			,String[]           headerVals
			,Map<String,String> respHeader) throws Exception {
		InputStream  is  = null; //信息读入流
		try {
			is  = HttpCall.call(siVO.url()+subUrl,postData,contentType,headerKeys,headerVals,respHeader,"UTF-8");
			return FNodeHandler.getNodeHandler(is);
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
		}
	}
	
	
	/**
	 * 向集群中指定服务器发起请求（返回XML对象）
	 * @param siVO        集群中服务器信息类
	 * @param subUrl      调用目标的相对路径
	 * @param postData    提交数据对象 XML和JSON 也可以是字符串类型
	 * @param contentType 提交数据类型 0key=value&key=value  1XML 2JSON
	 * @param header      报文头数据
	 * @return            返回XML对象
	 * @throws Exception  异常
	 * 2019年9月5日
	 * @author MBG
	 */
	public IViewHandler postRvh(
			ServerInfoVO        siVO
			,String             subUrl
			,Object             postData
			,int                contentType
			,Map<String,String> header) throws Exception {
		InputStream  is  = null; //信息读入流
		try {
			is  = HttpCall.call(siVO.url()+subUrl,postData,contentType,header,"UTF-8");
			return FNodeHandler.getNodeHandler(is);
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
		}
	}
	
	
	/**
	 * 广播集群中所有服务器
	 * 注意：集群类中不包括本机服务器，本机服务器由原程序执行，否则会导致死循环
	 * @param scriptId   脚本主键
	 * @param param      提交参数
	 * 2016年11月21日
	 * @author MBG
	 */
	@Override
    public void callAll(String scriptId, Object param) {
		if(scriptId==null || scriptId.length()<1) {
			return;
		}
		//拼装提交值
		StringBuffer xmlSbf = new StringBuffer();
		xmlSbf
			.append("<?xml version=\"1.0\" encoding='UTF-8'?>")
			.append("<root>")
			.append("<script>").append(scriptId).append("</script>")
			.append("<sn>").append(getSn()).append("</sn>");
		if(param!=null) {
			xmlSbf.append(getSerializer().serialize(param)); 
		}
		xmlSbf.append("</root>");

		//当前群组的服务器序列
		SListMap<ServerInfoVO> sVec = groupServerMap.get(localServerVO.group);
		if(sVec==null) {
			return;
		}
		//构建头信息容器
		Map<String,String> headerMap = new HashMap<String,String>();
		headerMap.put("_cluster_mode_","2");
		headerMap.put("_cluster_call_",localServerVO.name);
		String sName; //服务器名
		ServerInfoVO sVO; //服务器信息
		for(int i=0;i<sVec.size();i++) {
			sVO = sVec.get(i);
			
			//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
			//if(sVO==null || sVO.disabled || !sVO.validSend || sVO.invalidServer) {
			
			if(sVO==null || sVO.disabled || !sVO.validSend) {
				continue;
			}
			sName = sVec.getKey(i);
			if(!localServerVO.name.equals(sName)) {
				try {
					HttpCall.xCall(sVO.url+sVO.context+CLUSTER_ACTION,xmlSbf.toString(),1,headerMap);
				}catch(Exception e) {
					e.getMessage();
				}
			}
		}
	}
	
	/**
	 * 广播集群中所有服务器
	 * 注意：集群类中不包括本机服务器，本机服务器由原程序执行，否则会导致死循环
	 * @param scriptId    脚本主键 
	 * 2016年11月21日
	 * @author MBG
	 */
	@Override
    public void callAll(String scriptId) {
		callAll(scriptId,null);
	}
	

	/**
	 * 调用指定服务器的动作
	 * @param serverName   集群内服务器名（主键）
	 * @param action       动作路径（不包含域名，ip，虚拟路径）
	 * @param params       提交参数对象，支持 String IViewHandler Json
	 * @return             调用动作获取的返回值
	 * @throws Exception   异常
	 * 2018年6月26日
	 * @author MBG
	 */
	public String callAction(String serverName,String action,Object params) throws Exception {
		//获取目标服务器信息
		ServerInfoVO siVO = getServerInfo(serverName);
		//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
		//if(siVO==null || siVO.disabled || siVO.invalidServer || !siVO.validSend) {
		if(siVO==null || siVO.disabled || !siVO.validSend) {
			warning("====ClusterFilter callAction 无法调用目标服务器 action:["+action+"] params:["+params+"] serverName:["+serverName+"] VO:"+siVO);
			return "";
		}
		//构建头信息容器
		Map<String,String> headerMap = new HashMap<String,String>();
		headerMap.put("_cluster_call_",localServerVO.name);
		return HttpCall.sCall(siVO.url()+action,params,headerMap);
	}
	
	
	/**
	 * 下载指定服务器文件
	 * @param serverName  集群内服务器名（主键）
	 * @param action      动作路径（不包含域名，ip，虚拟路径）
	 * @param params      提交参数对象，支持 String IViewHandler Json
	 * @return            文件读入流
	 * @throws Exception  异常
	 * 2018年6月26日
	 * @author MBG
	 */
	public InputStream download(String serverName,String action,String params) throws Exception {
		//获取目标服务器信息
		ServerInfoVO siVO = getServerInfo(serverName);
		
		//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
		//if(siVO==null || siVO.disabled || siVO.invalidServer || !siVO.validSend) {
		
		if(siVO==null || siVO.disabled || !siVO.validSend) {
			warning("====ClusterFilter download 无法下载目标服务器文件 action:["+action+"] serverName:["+serverName+"] VO:"+siVO);
			return null;
		}
		//头容器
		Map<String,String> header = new HashMap<>();
		header.put("_cluster_call_",localServerVO.name); //放入本机名
		return HttpCall.download(siVO.url()+action,params,null,header);
	}
	

	/**
	 * 判断是否由集群成员调用的
	 * 动作上下文（request，response）是从线程会话中获取的。
	 * 所以不支持服务类，只支持动作类
	 * IServletConst.KEY_ACTION_CONTEXT
	 * @return 是否由集群成员调用
	 * 2016年11月20日
	 * @author MBG
	 */
	@Override
    public boolean check() {
        //获取线程会话
        IActionContext ac = (IActionContext)ThreadSession.get(IServletConst.KEY_ACTION_CONTEXT);
        return check(ac);
	}
	
	/**
	 * 判断是否由集群成员调用的
	 * 动作上下文（request，response）是从线程会话中获取的。
	 * 所以不支持服务类，只支持动作类
	 * IServletConst.KEY_ACTION_CONTEXT
	 * @param ac 动作上下文
	 * @return 是否由集群成员调用
	 * 2016年11月20日
	 * @author MBG
	 */
	@Override
    public boolean check(IActionContext ac) {
        if(ac==null) {
        	return false;
        }
        return ipList.contains(ac.getRemoteAddress());
	}
	
	/**
	 * 判断是否由集群成员调用的
	 * 所以不支持服务类，只支持动作类
	 * @param req 页面请求
	 * @return 是否由集群成员调用
	 * 2016年11月20日
	 * @author MBG
	 */
	public boolean check(IRequest req) {
		if(req==null) {
			return false;
		}
		return ipList.contains(req.getRemoteAddr());
	}
	
	/**
	 * 返回当前服务器是否为主服务器
	 * @return 当前服务器是否为主服务器
	 * 2016年11月21日
	 * @author MBG
	 */
	@Override
    public boolean master() {
		if(clusterMode==0) {
			//还没初始化完毕，无法确定是否为主服务器
			return false;
		}
		if(clusterMode==2) {
			//非集群模式下，每台服务器都是独立的，都是主服务器
			return true;
		}
		return localServerVO.master;
	}
	
	/**
	 * 是否为集群模式
	 * @return 是否为集群模式
	 * 2016年11月28日
	 * @author MBG
	 */
	@Override
    public boolean clusterMode() {
		return clusterMode==1;
	}
	
	/**
	 * 集群服务器索引（当前服务器在集群服务器中的位置）
	 * @return 集群服务器索引
	 * 2016年12月11日
	 * @author MBG
	 */
	@Override
    public int index() {
		return localServerVO.serverIndex;
	}
	
	/**
	 * 覆盖函数
	 */
	@Override
    public String toString() {
		//构建返回值
		StringBuffer reSbf = new StringBuffer();
		reSbf
		  .append("\n---------------------------------------\nDisabled:")
		  .append(disabled)
		  .append("\nclusterMode:[")
		  .append(clusterMode)
		  .append("] e.g. 0:init  1enabled 2disabled\n")
		  .append("Local:\n name:")
		  .append(name())
		  .append("\n\n ServerList:\n");
		
		ServerInfoVO ele; //服务器信息容器
		for(int i=0;i<serverMap.size();i++) {
			ele = serverMap.get(i);
			reSbf
			  .append("\n+++++++\n index:[")
			  .append(i)
			  .append("]\n name:")
			  .append(ele.name)
			  .append("\n ip:")
			  .append(ele.ip)
			  .append("\n url:")
			  .append(ele.url)
			  .append("\n context:")
			  .append(ele.context)
			  .append("\n disabled:")
			  .append(ele.disabled)
			  .append("\n validSend:")
			  .append(ele.validSend)
			  .append("\n validReceive:")
			  .append(ele.validReceive)
			  .append("\n");
		}
		reSbf.append("\n---------------------------------------\n");
		return reSbf.toString();
	}
	
	/**
	 * 获取指定分组的服务器序列
	 * @param group 分组名
	 * @return 服务器序列
	 * 2017年3月28日
	 * @author MBG
	 */
	public List<ServerInfoVO> getServerInfoList(String group){
		//获取指定分组的服务器信息序列
		SListMap<ServerInfoVO> sVec = groupServerMap.get(group);
		if(sVec==null) {
			return new ArrayList<ServerInfoVO>();
		}
		return sVec.values();
	}
	
	/**
	 * 获取当前群组中，其它服务器信息序列（除去本机）
	 * @return 其它服务器信息序列
	 * 2019年6月13日
	 * @author MBG
	 */
	public List<ServerInfoVO> getServerInfoGroupOther(){
		//获取指定分组的服务器信息序列
		SListMap<ServerInfoVO> sVec = groupServerMap.get(localServerVO.group);
		//构建返回值
		List<ServerInfoVO> reList = new ArrayList<ServerInfoVO>();
		ServerInfoVO ele; //服务器信息元素
		for(int i=0;i<sVec.size();i++) {
			ele = sVec.get(i);
			if(ele.local) {
				continue;
			}
			reList.add(ele);
		}
		return reList;
	}
	
	/**
	 * 获取指定分组的服务器配置信息容器
	 * @param group 分组名
	 * @return 服务器配置信息容器
	 * 2017年3月28日
	 * @author MBG
	 */
	public SListMap<ServerInfoVO> getServerInfos(String group){
		//获取指定分组的服务器信息序列
		SListMap<ServerInfoVO> sVec = groupServerMap.get(group);
		if(sVec==null) {
			return new SListMap<ServerInfoVO>();
		}
		return sVec;
	}
	
	
	/**
	 * 获取指定服务器信息
	 * @param serverName 服务器名 
	 * @return 服务器信息类
	 * 2017年4月19日
	 * @author MBG
	 */
	public ServerInfoVO getServerInfo(String serverName) {
		if(serverMap.containsKey(serverName)) {
			return serverMap.get(serverName);
		}
		if(groupServerMap.containsKey(serverName)) {
			//如果传入的是集群分组的名字，则返回分组中负载压力最小的服务器信息
			if(invokeLoadBalancing) {
				return getBalanceServer(serverName);
			}
			return getMasterServer(serverName);
		}
		return null;
	}
	
	/**
	 * 获取指定服务器（或指定群组中负载均衡分配的服务器url）
	 * @param serverName 服务器名，或群组名（由群组分配一台服务器返回ｕｒｌ）
	 * @return　　　　　　　服务器ｕｒｌ
	 * 2019年8月3日
	 * @author MBG
	 */
	@Override
    public String getServerUrl(String serverName) {
		//获取指定服务器信息
		ServerInfoVO siVO = getServerInfo(serverName);
		if(siVO==null) {
			return "";
		}
		return siVO.url();
	}
	
	
	/**
	 * 获取指定群组中的主服务器
	 * @param groupName 群组名
	 * @return 主服务器信息
	 * 2017年4月21日
	 * @author MBG
	 */
	@Override
    public ServerInfoVO getMasterServer(String groupName) {
		if(groupName==null || groupName.length()<1) {
			return null;
		}
		//获取指定群组的主服务器信息
		SListMap<ServerInfoVO> gsMap = groupServerMap.get(groupName);
		if(gsMap==null) {
			return null;
		}
		ServerInfoVO fEle = null; //集群中第一台服务器
		for(ServerInfoVO ele:gsMap.values()) {
			if(fEle==null) {
				fEle = ele;
			}
			if(ele.master) {
				return ele;
			}
		}
		return fEle;
	}
	
	/**
	 * 通过负载均衡规则获取相对空闲服务器
	 * @param groupName 群组名
	 * @return 相对空闲服务器
	 * 2017年6月16日
	 * @author MBG
	 */
	public ServerInfoVO getBalanceServer(String groupName) {
		//获取该分组中的服务器序列
		SListMap<ServerInfoVO> gsMap = groupServerMap.get(groupName);
		ServerInfoVO siVO; //目标服务器元素
		ServerInfoVO reSiVO = null; //返回服务器信息
		for(int i=0;i<gsMap.size();i++) {
			siVO = gsMap.get(i);
			
			//已弃用，请看变量ServerInfoVO.invalidServer声明处的注释
			//if(siVO!=null && !siVO.disabled && !siVO.invalidServer) {
			
			if(siVO!=null && !siVO.disabled) {
				if(reSiVO==null 
						|| (reSiVO.invokeCount>siVO.invokeCount 
								&& reSiVO.invokeClearFlag>=reSiVO.invokeClearFlag)) {
					reSiVO = siVO;
				}
			}
		}
		return reSiVO;
	}
	
	/**
	 * 是否禁用
	 * @return 是否禁用
	 * 2017年4月26日
	 * @author MBG
	 */
	@Override
    public boolean disabled() {
		return disabled;
	}
	
	/**
	 * 获取集群信息配置序列
	 * @return 集群信息配置序列
	 * 2016年12月26日
	 * @author MBG
	 */
	public List<ServerInfoVO> getServerInfoList(){
		return serverMap.values();
	}
	
	/**
	 * 获取集群信息配置容器
	 * @return 集群配置信息容器
	 * 2016年12月26日
	 * @author MBG
	 */
	public SListMap<ServerInfoVO> getServerInfo(){
		return serverMap;
	}
	
	/**
	 * 执行注册
	 * 
	 * 设计时，原本加入了当前类工厂作为参数，
	 * 但后来发现，如果是递归注册（注册类中也声明了其它注册类）时
	 * 无法将注册类对应的类加载器传入
	 * 
	 * 刘虻
	 * 2010-4-22 上午10:18:37
	 * @param bean 待注册的类
	 * @return 是否注册成功
	 */
	@Override
	public boolean regist(Object bean) {
		if(bean==null) {
			return false;
		}
		if(bean instanceof ITriggerInit) {
			if(initBeanList.contains(bean)) {
				return true;
			}
			initBeanList.add((ITriggerInit)bean);
			if(findMeNameOK) {
				try {
					((ITriggerInit)bean).init(this);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
			return true;
		}
		if(bean instanceof IClusterTrigger) {
			if(triggerBeans.containsValue(bean)) {
				return true;
			}
			triggerBeans.put(((IClusterTrigger)bean).triggerKey(),(IClusterTrigger)bean);
			if(findMeNameOK) {
				try {
					((IClusterTrigger)bean).clusterInited(this);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
			return true;
		}
		return false;
	}
	

	/**
	 * 判断本次调用是否由集群中的服务器发起
	 * @param req    请求对象
	 * @return       是否由集群中的服务器发起
	 * 2018年6月28日
	 * @author MBG
	 */
	@Override
    public boolean callFromCluster(IRequest req) {
		//来源服务器集群主键
		String sName = str(req.getHeader("_cluster_call_"));
		if(sName.length()<1) {
			return false;
		}
		//获取服务器信息
		ServerInfoVO siVO = getServerInfo(sName);
		if(siVO==null) {
			return false;
		}
		return siVO.ip.equals(req.getRemoteAddr());
	}

	/**
	 * 执行反注册
	 * @param bean 需要做反注册的类
	 * 2014年7月31日
	 * @author 马宝刚
	 */
	@Override
	public void unRegist(Object bean) {
		initBeanList.remove(bean);
	}
	
	/**
	 * 获取当前服务器信息VO
	 * @return 当前服务器信息VO
	 * 2018年7月2日
	 * @author MBG
	 */
	public ServerInfoVO getLocalServerInfoVO() {
		return localServerVO;
	}
	
	/**
	 * 调用目标内部触发器 (调用的目标只能是当前同样的类） 返回得报文头信息也放在了header中
	 * @param siVO         目标服务器信息类
	 * @param param        提交参数对象
	 * @param contentType  0html  1xml  2json
	 * @param caller       调用者（当前类实例）
	 * @return             返回信息读入流
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
    public InputStream triggerCall(
			ServerInfoVO siVO,Object param,Map<String,String> header,int contentType,IClusterTrigger caller) throws Exception {
		//构建返回值
		if(header==null) {
			header = new HashMap<String,String>();
		}
		header.put("cluster_trigger_key",caller.triggerKey());
		header.put("_cluster_key",name());
		header.put("_cluster_mode_","8");
		ServerInfoVO callVO = getExecutableServer(siVO.name,null,new ArrayList<String>(),header);
		if(callVO==null) {
			throw new Exception("Not Find The Target Server :["+siVO.name+"]");
		}
		return HttpCall.call(callVO.url()+CLUSTER_ACTION,param,contentType,header,"UTF-8");
	}
	
	
	/**
	 * 获取按照分组保存的服务器信息容器
	 * @return 按照分组保存的服务器信息容器
	 * 2020年7月14日
	 * @author MBG
	 */
	public Map<String,SListMap<ServerInfoVO>> getGroupServerMap(){
		return groupServerMap;
	}
	
	/**
	 * 获取有效的序列化处理类
	 * @return 有效的序列化处理类
	 * 2019年12月23日
	 * @author MBG
	 */
	private synchronized XmlSerializer getSerializer() {
		if(xmlSerializer==null) {
			xmlSerializer = bean(XmlSerializer.class);
		}
		return xmlSerializer;
	}
	
	/**
	 * 返回序列号
	 * @return 序列号
	 * 2020年4月15日
	 * @author MBG
	 */
	private synchronized long getSn() {
		snIndex++;
		if(snIndex>=Long.MAX_VALUE) {
			snIndex = 0; 
		}
		return snIndex;
	}
}
